diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index a696e407c1..15f4baeaa1 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -67,7 +67,7 @@ nix develop -c cargo test --workspace If the end-of-session gate fails during `./prep-all.sh`, run these steps sequentially so dependencies still build: ```bash nix develop -c forge install -nix develop -c bash -c '(cd lib/rain.interpreter && rainix-sol-prelude && rainix-rs-prelude && i9r-prelude)' +nix develop -c bash -c '(cd lib/rain.interpreter && rainix-sol-prelude && rainix-rs-prelude && rainlang-prelude)' nix develop -c bash -c '(cd lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.math.float && rainix-sol-prelude && rainix-rs-prelude)' nix develop -c bash -c '(cd lib/rain.interpreter/lib/rain.metadata && rainix-sol-prelude && rainix-rs-prelude)' nix develop -c rainix-sol-prelude && nix develop -c rainix-rs-prelude && nix develop -c raindex-prelude @@ -79,4 +79,4 @@ nix develop -c npm run build -w @rainlanguage/webapp Goal: all CI checks in `.github/workflows` pass. Be patient with long builds/tests and never commit with failing lint/tests. - \ No newline at end of file + diff --git a/Cargo.lock b/Cargo.lock index 3ddcf4c3b4..0c360ac26f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6717,22 +6717,6 @@ dependencies = [ "thiserror 1.0.69", ] -[[package]] -name = "rain-interpreter-eval" -version = "0.1.0" -dependencies = [ - "alloy", - "eyre", - "foundry-evm", - "rain-error-decoding 0.1.0 (git+https://github.com/rainlanguage/rain.error?rev=3d2ed70fb2f7c6156706846e10f163d1e493a8d3)", - "rain_interpreter_bindings", - "revm 24.0.1", - "revm 25.0.0", - "serde", - "thiserror 1.0.69", - "wasm-bindgen-utils 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rain-math-float" version = "0.1.0" @@ -6830,40 +6814,6 @@ dependencies = [ "alloy", ] -[[package]] -name = "rain_interpreter_bindings" -version = "0.1.0" -dependencies = [ - "alloy", -] - -[[package]] -name = "rain_interpreter_dispair" -version = "0.1.0" -dependencies = [ - "alloy", -] - -[[package]] -name = "rain_interpreter_parser" -version = "0.1.0" -dependencies = [ - "alloy", - "alloy-ethers-typecast 0.2.0 (git+https://github.com/rainlanguage/alloy-ethers-typecast?rev=bcc3a04394aefe191fef4ae8e6e94381a419c99a)", - "rain_interpreter_bindings", - "rain_interpreter_dispair", - "thiserror 1.0.69", - "tokio", -] - -[[package]] -name = "rain_interpreter_test_fixtures" -version = "0.0.0" -dependencies = [ - "alloy", - "getrandom 0.2.16", -] - [[package]] name = "rain_orderbook_app_settings" version = "0.0.0-alpha.0" @@ -6916,13 +6866,13 @@ dependencies = [ "itertools 0.14.0", "rain-math-float", "rain-metadata 0.0.2-alpha.6", - "rain_interpreter_bindings", "rain_orderbook_app_settings", "rain_orderbook_bindings", "rain_orderbook_common", "rain_orderbook_quote", "rain_orderbook_subgraph_client", "rain_orderbook_test_fixtures", + "rainlang_bindings", "rusqlite", "rust-bigint", "serde", @@ -6943,6 +6893,8 @@ dependencies = [ "alloy-ethers-typecast 0.2.0 (git+https://github.com/rainlanguage/alloy-ethers-typecast?rev=bcc3a04394aefe191fef4ae8e6e94381a419c99a)", "async-trait", "backon", + "base64 0.22.1", + "bincode", "chrono", "csv", "dotrain", @@ -6957,19 +6909,19 @@ dependencies = [ "once_cell", "proptest", "rain-error-decoding 0.1.0 (git+https://github.com/rainlanguage/rain.error?rev=3d2ed70fb2f7c6156706846e10f163d1e493a8d3)", - "rain-interpreter-eval", "rain-math-float", "rain-metaboard-subgraph", "rain-metadata 0.0.2-alpha.6", "rain-metadata-bindings", - "rain_interpreter_bindings", - "rain_interpreter_dispair", - "rain_interpreter_parser", "rain_orderbook_app_settings", "rain_orderbook_bindings", "rain_orderbook_quote", "rain_orderbook_subgraph_client", "rain_orderbook_test_fixtures", + "rainlang-eval", + "rainlang_bindings", + "rainlang_dispair", + "rainlang_parser", "reqwest 0.12.20", "rusqlite", "serde", @@ -6977,6 +6929,7 @@ dependencies = [ "serde_bytes", "serde_json", "serde_yaml", + "sha2 0.10.9", "strict-yaml-rust", "tempfile", "thiserror 1.0.69", @@ -7059,7 +7012,6 @@ dependencies = [ "httpmock", "once_cell", "rain-error-decoding 0.1.0 (git+https://github.com/rainlanguage/rain.error?rev=3d2ed70fb2f7c6156706846e10f163d1e493a8d3)", - "rain-interpreter-eval", "rain-math-float", "rain-metadata 0.0.2-alpha.6", "rain_orderbook_app_settings", @@ -7067,6 +7019,7 @@ dependencies = [ "rain_orderbook_common", "rain_orderbook_subgraph_client", "rain_orderbook_test_fixtures", + "rainlang-eval", "reqwest 0.12.20", "serde", "serde_json", @@ -7129,10 +7082,60 @@ dependencies = [ "alloy", "getrandom 0.2.16", "rain-math-float", - "rain_interpreter_test_fixtures", + "rainlang_test_fixtures", "serde_json", ] +[[package]] +name = "rainlang-eval" +version = "0.1.0" +dependencies = [ + "alloy", + "eyre", + "foundry-evm", + "rain-error-decoding 0.1.0 (git+https://github.com/rainlanguage/rain.error?rev=3d2ed70fb2f7c6156706846e10f163d1e493a8d3)", + "rainlang_bindings", + "revm 24.0.1", + "revm 25.0.0", + "serde", + "thiserror 1.0.69", + "wasm-bindgen-utils 0.0.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rainlang_bindings" +version = "0.1.0" +dependencies = [ + "alloy", +] + +[[package]] +name = "rainlang_dispair" +version = "0.1.0" +dependencies = [ + "alloy", +] + +[[package]] +name = "rainlang_parser" +version = "0.1.0" +dependencies = [ + "alloy", + "alloy-ethers-typecast 0.2.0 (git+https://github.com/rainlanguage/alloy-ethers-typecast?rev=bcc3a04394aefe191fef4ae8e6e94381a419c99a)", + "rainlang_bindings", + "rainlang_dispair", + "thiserror 1.0.69", + "tokio", +] + +[[package]] +name = "rainlang_test_fixtures" +version = "0.0.0" +dependencies = [ + "alloy", + "getrandom 0.2.16", +] + [[package]] name = "rand" version = "0.8.5" diff --git a/Cargo.toml b/Cargo.toml index 53847f114e..83dccc9a77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,10 +42,10 @@ dotrain-lsp = "6.0.1-alpha.24" rain-metadata = { path = "lib/rain.interpreter/lib/rain.metadata/crates/cli" } rain-metadata-bindings = { path = "lib/rain.interpreter/lib/rain.metadata/crates/bindings" } rain-metaboard-subgraph = { path = "lib/rain.interpreter/lib/rain.metadata/crates/metaboard" } -rain_interpreter_bindings = { path = "lib/rain.interpreter/crates/bindings" } -rain_interpreter_dispair = { path = "lib/rain.interpreter/crates/dispair" } -rain_interpreter_parser = { path = "lib/rain.interpreter/crates/parser" } -rain-interpreter-eval = { path = "lib/rain.interpreter/crates/eval" } +rain_interpreter_bindings = { package = "rainlang_bindings", path = "lib/rain.interpreter/crates/bindings" } +rain_interpreter_dispair = { package = "rainlang_dispair", path = "lib/rain.interpreter/crates/dispair" } +rain_interpreter_parser = { package = "rainlang_parser", path = "lib/rain.interpreter/crates/parser" } +rain-interpreter-eval = { package = "rainlang-eval", path = "lib/rain.interpreter/crates/eval" } csv = "1.3.0" insta = { version = "1.34.0" } proptest = "1.4.0" diff --git a/ai_commands/sdk-documentation-update.md b/ai_commands/sdk-documentation-update.md index f3d2709a72..c4c00072b0 100644 --- a/ai_commands/sdk-documentation-update.md +++ b/ai_commands/sdk-documentation-update.md @@ -107,4 +107,4 @@ What to return Notes - This repo uses a macro named `wasm_export` in `crates/js_api` to define JS-visible APIs and attach TypeScript-specific metadata (`js_name`, `unchecked_return_type`, `param_description`, `return_description`, `preserve_js_class`). Treat those attributes as the contract for the generated TypeScript surface. -- Some examples in README demonstrate end-to-end flows (e.g., GUI setup via `DotrainOrderGui`, order hash/calldata helpers). Prefer aligning those with current tests under `packages/orderbook/test/js_api` to avoid drift. +- Some examples in README demonstrate end-to-end flows (e.g., builder setup via `RaindexOrderBuilder`, order hash/calldata helpers). Prefer aligning those with current tests under `packages/orderbook/test/js_api` to avoid drift. diff --git a/crates/bindings/ARCHITECTURE.md b/crates/bindings/ARCHITECTURE.md index 1cb8c4c4f9..ae7da99398 100644 --- a/crates/bindings/ARCHITECTURE.md +++ b/crates/bindings/ARCHITECTURE.md @@ -8,7 +8,7 @@ File Layout - Cargo.toml: Declares the crate `rain_orderbook_bindings`. Key deps: `alloy` (codegen + types + RPC), `serde` (Serialize/Deserialize), `tower` (layers), `url`, `thiserror`. For WASM builds it uses `wasm-bindgen-utils` and `wasm-bindgen-test` for tests. - src/lib.rs: Declares contract bindings using Alloy’s `sol!` macro and re‑exports internal modules. Conditionally includes WASM modules. - src/provider.rs: Builds a read‑only provider with multi‑RPC fallback and sensible default request fillers. -- src/js_api.rs (wasm only): JS/WASM interop. Implements wasm conversion traits and custom TypeScript interfaces for selected ABI types used in the GUI. +- src/js_api.rs (wasm only): JS/WASM interop. Implements wasm conversion traits and custom TypeScript interfaces for selected ABI types used in the order builder. - src/wasm_traits.rs (wasm only): Utility trait to convert JS `BigInt` to `U256` with negative/overflow handling plus tests. Generated Solidity Bindings (Alloy `sol!`) @@ -34,7 +34,7 @@ Generated Solidity Bindings (Alloy `sol!`) - Derives and defaults: - `all_derives = true` enables useful traits on generated types, including `Clone`, `Debug`, `Default`, `Eq`, `PartialEq`, and more, which the codebase relies on (e.g., `OrderV4::default()`). - - `extra_derives(serde::Serialize, serde::Deserialize)` ensures all ABI types can be serialized to/from JSON, which is critical for CLI/GUI I/O and WASM interop. + - `extra_derives(serde::Serialize, serde::Deserialize)` ensures all ABI types can be serialized to/from JSON, which is critical for CLI/builder I/O and WASM interop. ABI Source of Truth - The referenced JSON files under `out/` are produced by Foundry (`forge build`). If ABIs change, re‑build the contracts so `out/.../*.json` stays in sync. The `sol!` macro reads these on compile. @@ -59,7 +59,7 @@ Provider Utilities (`src/provider.rs`) WASM and JS Interop - Conditional compilation: `src/js_api.rs` and `src/wasm_traits.rs` are compiled only for `target_family = "wasm"`. -- `src/js_api.rs` bridges key ABI types to predictable TypeScript shapes for the GUI, using the workspace’s `wasm-bindgen-utils` macros: +- `src/js_api.rs` bridges key ABI types to predictable TypeScript shapes for the order builder, using the workspace’s `wasm-bindgen-utils` macros: - `impl_wasm_traits!(T)` implements glue code to convert between Rust and JS values (e.g., Serde + wasm-bindgen interop) for the given type. - `impl_custom_tsify!(T, "…TS interface…")` pins the exact TypeScript surface for WASM consumers. This avoids relying on auto‑generated typings that may drift with upstream changes. @@ -93,7 +93,7 @@ How This Crate Is Used Elsewhere - Common utilities (`crates/common`): - Uses ABI‑generated call structs (e.g., `deposit3Call`, `withdraw3Call`, `removeOrder3Call`, `IERC20::approveCall`) to build calldata for transactions. - JS API (`crates/js_api`): - - Builds GUI calldata for approvals/deposits/add‑order and uses call structs like `OrderBook::multicallCall` without needing on‑chain RPC instance helpers. + - Builds order builder calldata for approvals/deposits/add‑order and uses call structs like `OrderBook::multicallCall` without needing on‑chain RPC instance helpers. - CLI (`crates/cli`): - Consumes ABI struct types such as `OrderV4` and `IOV2` to construct orders from user inputs. @@ -132,5 +132,5 @@ Limitations and Notes - `AnyNetwork` provider: Consumers are responsible for ensuring the supplied RPCs point to the intended chain. Updating Bindings -- Modify the Solidity contracts as needed, run `nix develop -c forge build`, then rebuild Rust. If new structs/functions are added to the ABIs, they will appear under the corresponding Rust modules after recompilation. Add/update WASM TS interfaces in `src/js_api.rs` as needed for GUI usage. +- Modify the Solidity contracts as needed, run `nix develop -c forge build`, then rebuild Rust. If new structs/functions are added to the ABIs, they will appear under the corresponding Rust modules after recompilation. Add/update WASM TS interfaces in `src/js_api.rs` as needed for order builder usage. diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index a5c1cebc53..8ed63db739 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -50,6 +50,9 @@ eyre.workspace = true rain-math-float.workspace = true tower.workspace = true flate2 = "1.0.34" +base64 = "0.22.1" +bincode = "1.3.3" +sha2 = "0.10.8" itertools = { workspace = true } async-trait = { workspace = true } web-sys = { version = "0.3", features = ["Window", "Navigator", "console"] } diff --git a/crates/common/src/add_order.rs b/crates/common/src/add_order.rs index d366fdc260..c1604c726d 100644 --- a/crates/common/src/add_order.rs +++ b/crates/common/src/add_order.rs @@ -27,7 +27,7 @@ use rain_interpreter_eval::{ }; use rain_interpreter_parser::{Parser2, ParserError, ParserV2}; use rain_metadata::{ - types::dotrain::gui_state_v1::DotrainGuiStateV1, + types::dotrain::order_builder_state_v1::OrderBuilderStateV1, types::raindex_signed_context_oracle::RaindexSignedContextOracleV1, ContentEncoding, ContentLanguage, ContentType, Error as RainMetaError, KnownMagic, RainMetaDocumentV1Item, }; @@ -327,11 +327,11 @@ impl AddOrderArgs { Some(meta_docs) => { match meta_docs .iter() - .find(|document| document.magic == KnownMagic::DotrainGuiStateV1) + .find(|document| document.magic == KnownMagic::OrderBuilderStateV1) { Some(doc) => { - let gui_state = DotrainGuiStateV1::try_from(doc.clone())?; - let subject_hash = gui_state.dotrain_hash(); + let builder_state = OrderBuilderStateV1::try_from(doc.clone())?; + let subject_hash = builder_state.dotrain_hash(); let meta_document = RainMetaDocumentV1Item { payload: ByteBuf::from(self.dotrain.as_bytes()), magic: KnownMagic::DotrainSourceV1, @@ -532,7 +532,7 @@ price: 2e18; fn test_try_generate_meta_with_additional_meta_filters_reserved() { let dotrain_body = "/* test */".to_string(); let dotrain_hash = DotrainSourceV1(dotrain_body.clone()).hash(); - let gui_state = DotrainGuiStateV1 { + let builder_state = OrderBuilderStateV1 { dotrain_hash, field_values: BTreeMap::new(), deposits: BTreeMap::new(), @@ -552,7 +552,7 @@ price: 2e18; // Should be filtered out RainMetaDocumentV1Item::from(DotrainSourceV1("ignored-dotrain".to_string())), // Should be retained - RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(), ]; let args = AddOrderArgs { @@ -570,23 +570,23 @@ price: 2e18; // Rainlang meta is always present and should match the composed rainlang payload assert_eq!(decoded[0].magic, KnownMagic::RainlangSourceV1); assert_eq!(decoded[0].payload.as_ref(), "rainlang-body".as_bytes()); - // Only the GUI state from additional meta should remain (reserved magics filtered) + // Only the builder state from additional meta should remain (reserved magics filtered) assert_eq!(decoded.len(), 2); - let gui_meta = decoded + let builder_meta = decoded .iter() - .find(|item| item.magic == KnownMagic::DotrainGuiStateV1) - .expect("gui state meta not found"); + .find(|item| item.magic == KnownMagic::OrderBuilderStateV1) + .expect("builder state meta not found"); assert_eq!( - DotrainGuiStateV1::try_from(gui_meta.clone()).unwrap(), - gui_state + OrderBuilderStateV1::try_from(builder_meta.clone()).unwrap(), + builder_state ); } #[test] - fn test_try_into_emit_meta_call_with_gui_state() { + fn test_try_into_emit_meta_call_with_builder_state() { let dotrain_body = "/* dotrain template */".to_string(); let dotrain_source = DotrainSourceV1(dotrain_body.clone()); - let gui_state = DotrainGuiStateV1 { + let builder_state = OrderBuilderStateV1 { dotrain_hash: dotrain_source.hash(), field_values: BTreeMap::new(), deposits: BTreeMap::new(), @@ -600,7 +600,9 @@ price: 2e18; outputs: vec![], bindings: HashMap::new(), rainlang: Address::default(), - additional_meta: Some(vec![RainMetaDocumentV1Item::try_from(gui_state).unwrap()]), + additional_meta: Some(vec![ + RainMetaDocumentV1Item::try_from(builder_state).unwrap() + ]), }; let emit_call = args @@ -616,10 +618,10 @@ price: 2e18; } #[test] - fn test_try_into_emit_meta_call_invalid_gui_state_payload() { - let invalid_gui_state = RainMetaDocumentV1Item { + fn test_try_into_emit_meta_call_invalid_builder_state_payload() { + let invalid_builder_state = RainMetaDocumentV1Item { payload: ByteBuf::from(vec![1, 2, 3]), - magic: KnownMagic::DotrainGuiStateV1, + magic: KnownMagic::OrderBuilderStateV1, content_type: ContentType::OctetStream, content_encoding: ContentEncoding::None, content_language: ContentLanguage::None, @@ -630,7 +632,7 @@ price: 2e18; outputs: vec![], bindings: HashMap::new(), rainlang: Address::default(), - additional_meta: Some(vec![invalid_gui_state]), + additional_meta: Some(vec![invalid_builder_state]), }; let err = args.try_into_emit_meta_call().unwrap_err(); diff --git a/crates/common/src/dotrain_order.rs b/crates/common/src/dotrain_order.rs index b1e3a0416c..e5a01110e4 100644 --- a/crates/common/src/dotrain_order.rs +++ b/crates/common/src/dotrain_order.rs @@ -727,9 +727,10 @@ impl DotrainOrder { StrictYaml::Hash(deployments_hash), ); - if let Some(gui_yaml) = Self::clone_gui_for_deployment(&documents, deployment_key.as_str())? + if let Some(builder_yaml) = + Self::clone_builder_config_for_deployment(&documents, deployment_key.as_str())? { - root_hash.insert(StrictYaml::String("gui".to_string()), gui_yaml); + root_hash.insert(StrictYaml::String("builder".to_string()), builder_yaml); } let scenario_yaml = Self::scenario_to_yaml(&scenario_cfg)?; let mut scenarios_hash = StrictYamlHash::new(); @@ -753,32 +754,32 @@ impl DotrainOrder { Ok(dotrain) } - fn clone_gui_for_deployment( + fn clone_builder_config_for_deployment( documents: &[Arc>], deployment_key: &str, ) -> Result, DotrainOrderError> { - let mut gui_value: Option = None; + let mut builder_value: Option = None; for document in documents { let document_read = document.read().map_err(|_| { DotrainOrderError::CleanUnusedFrontmatterError( - "Failed to read YAML document while cloning gui section".to_string(), + "Failed to read YAML document while cloning builder config section".to_string(), ) })?; if let StrictYaml::Hash(root_hash) = &*document_read { - if let Some(gui) = root_hash.get(&StrictYaml::String("gui".to_string())) { - gui_value = Some(gui.clone()); + if let Some(builder) = root_hash.get(&StrictYaml::String("builder".to_string())) { + builder_value = Some(builder.clone()); break; } } } - let Some(StrictYaml::Hash(mut gui_hash)) = gui_value else { + let Some(StrictYaml::Hash(mut builder_hash)) = builder_value else { return Ok(None); }; - let Some(deployments_yaml) = gui_hash + let Some(deployments_yaml) = builder_hash .get(&StrictYaml::String("deployments".to_string())) .cloned() else { @@ -787,7 +788,7 @@ impl DotrainOrder { let StrictYaml::Hash(deployments_hash) = deployments_yaml else { return Err(DotrainOrderError::CleanUnusedFrontmatterError( - "Gui deployments section is not a map".to_string(), + "Builder config deployments section is not a map".to_string(), )); }; @@ -804,12 +805,12 @@ impl DotrainOrder { deployment_yaml, ); - gui_hash.insert( + builder_hash.insert( StrictYaml::String("deployments".to_string()), StrictYaml::Hash(filtered_deployments), ); - Ok(Some(StrictYaml::Hash(gui_hash))) + Ok(Some(StrictYaml::Hash(builder_hash))) } fn strip_vault_ids_from_order(order_yaml: &mut StrictYaml) { @@ -1177,7 +1178,7 @@ deployments: scenarios: polygon: rainlang: polygon -gui: +builder: deployments: polygon-deployment: name: Used deployment @@ -1224,19 +1225,19 @@ _ _: 0 0; assert!(subgraphs.contains_key(&StrictYaml::String("sg-primary".to_string()))); assert!(!subgraphs.contains_key(&StrictYaml::String("sg-unused".to_string()))); - let StrictYaml::Hash(gui) = root - .get(&StrictYaml::String("gui".to_string())) - .expect("gui present") + let StrictYaml::Hash(builder_config) = root + .get(&StrictYaml::String("builder".to_string())) + .expect("builder config present") .clone() else { - panic!("gui not a hash"); + panic!("builder config not a hash"); }; - let StrictYaml::Hash(deployments) = gui + let StrictYaml::Hash(deployments) = builder_config .get(&StrictYaml::String("deployments".to_string())) - .expect("gui deployments present") + .expect("builder config deployments present") .clone() else { - panic!("gui deployments not a hash"); + panic!("builder config deployments not a hash"); }; assert!(deployments.contains_key(&StrictYaml::String("polygon-deployment".to_string()))); assert!(!deployments.contains_key(&StrictYaml::String("unused-deployment".to_string()))); diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index 38b5b3d70f..a2c3c1036f 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -10,6 +10,7 @@ pub mod meta; pub mod oracle; pub mod parsed_meta; pub mod raindex_client; +pub mod raindex_order_builder; pub mod rainlang; pub mod remove_order; #[cfg(not(target_family = "wasm"))] diff --git a/crates/common/src/parsed_meta.rs b/crates/common/src/parsed_meta.rs index 16d6949fc4..d5a4fc7e57 100644 --- a/crates/common/src/parsed_meta.rs +++ b/crates/common/src/parsed_meta.rs @@ -1,6 +1,6 @@ use rain_metadata::{ types::{ - dotrain::{gui_state_v1::DotrainGuiStateV1, source_v1::DotrainSourceV1}, + dotrain::{order_builder_state_v1::OrderBuilderStateV1, source_v1::DotrainSourceV1}, raindex_signed_context_oracle::RaindexSignedContextOracleV1, }, KnownMagic, RainMetaDocumentV1Item, @@ -14,7 +14,7 @@ use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; #[derive(Serialize, Deserialize, Debug, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] pub enum ParsedMeta { - DotrainGuiStateV1(DotrainGuiStateV1), + OrderBuilderStateV1(OrderBuilderStateV1), DotrainSourceV1(DotrainSourceV1), RaindexSignedContextOracleV1(RaindexSignedContextOracleV1), } @@ -30,9 +30,9 @@ impl ParsedMeta { item: &RainMetaDocumentV1Item, ) -> Result, rain_metadata::Error> { match item.magic { - KnownMagic::DotrainGuiStateV1 => { - let gui_state = DotrainGuiStateV1::try_from(item.clone())?; - Ok(Some(ParsedMeta::DotrainGuiStateV1(gui_state))) + KnownMagic::OrderBuilderStateV1 => { + let builder_state = OrderBuilderStateV1::try_from(item.clone())?; + Ok(Some(ParsedMeta::OrderBuilderStateV1(builder_state))) } KnownMagic::DotrainSourceV1 => { let source = DotrainSourceV1::try_from(item.clone())?; @@ -70,7 +70,7 @@ impl ParsedMeta { #[cfg(test)] mod tests { use alloy::{hex::FromHex, primitives::Address}; - use rain_metadata::types::dotrain::gui_state_v1::{ShortenedTokenCfg, ValueCfg}; + use rain_metadata::types::dotrain::order_builder_state_v1::{ShortenedTokenCfg, ValueCfg}; use super::*; use std::collections::BTreeMap; @@ -78,8 +78,8 @@ mod tests { fn get_default_dotrain_source() -> DotrainSourceV1 { DotrainSourceV1("default source".to_string()) } - fn get_default_dotrain_gui_state() -> DotrainGuiStateV1 { - DotrainGuiStateV1 { + fn get_default_dotrain_builder_state() -> OrderBuilderStateV1 { + OrderBuilderStateV1 { dotrain_hash: get_default_dotrain_source().hash(), field_values: BTreeMap::from([( "field1".to_string(), @@ -111,15 +111,15 @@ mod tests { } #[test] - fn test_from_meta_item_gui_state_v1() { - let gui_state = get_default_dotrain_gui_state(); - let item = RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(); + fn test_from_meta_item_order_builder_state_v1() { + let builder_state = get_default_dotrain_builder_state(); + let item = RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(); let result = ParsedMeta::from_meta_item(&item).unwrap(); match result.unwrap() { - ParsedMeta::DotrainGuiStateV1(parsed_gui_state) => { - assert_eq!(parsed_gui_state, gui_state); + ParsedMeta::OrderBuilderStateV1(parsed_builder_state) => { + assert_eq!(parsed_builder_state, builder_state); } - _ => panic!("Expected DotrainGuiStateV1"), + _ => panic!("Expected OrderBuilderStateV1"), } } @@ -140,11 +140,11 @@ mod tests { #[test] fn test_parse_multiple_items() { - let gui_state = get_default_dotrain_gui_state(); + let builder_state = get_default_dotrain_builder_state(); let source = get_default_dotrain_source(); let items = vec![ - RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(), RainMetaDocumentV1Item::from(source.clone()), ]; @@ -152,10 +152,10 @@ mod tests { assert_eq!(results.len(), 2); match &results[0] { - ParsedMeta::DotrainGuiStateV1(parsed_gui_state) => { - assert_eq!(*parsed_gui_state, gui_state); + ParsedMeta::OrderBuilderStateV1(parsed_builder_state) => { + assert_eq!(*parsed_builder_state, builder_state); } - _ => panic!("Expected DotrainGuiStateV1"), + _ => panic!("Expected OrderBuilderStateV1"), } match &results[1] { @@ -184,7 +184,7 @@ mod tests { fn test_parse_from_bytes_valid() { // Arrange two items let source = DotrainSourceV1("hello".to_string()); - let gui = DotrainGuiStateV1 { + let builder = OrderBuilderStateV1 { dotrain_hash: source.hash(), field_values: std::collections::BTreeMap::new(), deposits: std::collections::BTreeMap::new(), @@ -194,7 +194,7 @@ mod tests { }; let items = vec![ RainMetaDocumentV1Item::from(source.clone()), - RainMetaDocumentV1Item::try_from(gui.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder.clone()).unwrap(), ]; let bytes = RainMetaDocumentV1Item::cbor_encode_seq(&items, KnownMagic::RainMetaDocumentV1) .unwrap(); @@ -204,7 +204,7 @@ mod tests { // Assert assert!(matches!(&parsed[0], ParsedMeta::DotrainSourceV1(s) if s.0 == source.0)); - assert!(matches!(&parsed[1], ParsedMeta::DotrainGuiStateV1(g) if g == &gui)); + assert!(matches!(&parsed[1], ParsedMeta::OrderBuilderStateV1(g) if g == &builder)); } #[test] diff --git a/crates/common/src/raindex_client/orders.rs b/crates/common/src/raindex_client/orders.rs index 7330086285..5969223067 100644 --- a/crates/common/src/raindex_client/orders.rs +++ b/crates/common/src/raindex_client/orders.rs @@ -304,10 +304,10 @@ impl RaindexOrder { _ => None, }) } - #[wasm_bindgen(getter = dotrainGuiState)] - pub fn dotrain_gui_state(&self) -> Option { + #[wasm_bindgen(getter = orderBuilderState)] + pub fn order_builder_state(&self) -> Option { self.parsed_meta().into_iter().find_map(|meta| match meta { - ParsedMeta::DotrainGuiStateV1(state) => serde_json::to_string(&state).ok(), + ParsedMeta::OrderBuilderStateV1(state) => serde_json::to_string(&state).ok(), _ => None, }) } @@ -389,9 +389,9 @@ impl RaindexOrder { _ => None, }) } - pub fn dotrain_gui_state(&self) -> Option { + pub fn order_builder_state(&self) -> Option { self.parsed_meta().into_iter().find_map(|meta| match meta { - ParsedMeta::DotrainGuiStateV1(state) => serde_json::to_string(&state).ok(), + ParsedMeta::OrderBuilderStateV1(state) => serde_json::to_string(&state).ok(), _ => None, }) } @@ -1439,8 +1439,8 @@ impl RaindexOrder { return Ok(()); } - let dotrain_gui_state = match self.parsed_meta.iter().find_map(|meta| { - if let ParsedMeta::DotrainGuiStateV1(state) = meta { + let order_builder_state = match self.parsed_meta.iter().find_map(|meta| { + if let ParsedMeta::OrderBuilderStateV1(state) = meta { Some(state.clone()) } else { None @@ -1455,7 +1455,7 @@ impl RaindexOrder { None => return Ok(()), }; - let subject_hash = dotrain_gui_state.dotrain_hash(); + let subject_hash = order_builder_state.dotrain_hash(); let metabytes = match client .get_metabytes_by_subject(&MetaBigInt(alloy::hex::encode_prefixed(subject_hash))) @@ -1619,7 +1619,7 @@ mod tests { use httpmock::MockServer; use rain_math_float::Float; use rain_metadata::types::dotrain::{ - gui_state_v1::DotrainGuiStateV1, source_v1::DotrainSourceV1, + order_builder_state_v1::OrderBuilderStateV1, source_v1::DotrainSourceV1, }; use rain_metadata::{ ContentEncoding, ContentLanguage, ContentType, KnownMagic, RainMetaDocumentV1Item, @@ -1638,8 +1638,8 @@ mod tests { DotrainSourceV1("sample dotrain source".to_string()) } - fn sample_dotrain_gui_state(source: &DotrainSourceV1) -> DotrainGuiStateV1 { - DotrainGuiStateV1 { + fn sample_order_builder_state(source: &DotrainSourceV1) -> OrderBuilderStateV1 { + OrderBuilderStateV1 { dotrain_hash: source.hash(), field_values: BTreeMap::new(), deposits: BTreeMap::new(), @@ -1679,10 +1679,10 @@ mod tests { #[tokio::test] async fn try_from_sg_order_populates_parsed_meta() { let source = sample_dotrain_source(); - let gui_state = sample_dotrain_gui_state(&source); + let builder_state = sample_order_builder_state(&source); let meta_hex = encode_meta_items_hex(vec![ RainMetaDocumentV1Item::from(source.clone()), - RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(), ]); let mut sg_order = get_order1(); @@ -1706,18 +1706,18 @@ mod tests { assert_eq!(order.parsed_meta().len(), 2); assert_eq!(order.dotrain_source(), Some(source.0)); - let parsed_gui_state: DotrainGuiStateV1 = - serde_json::from_str(&order.dotrain_gui_state().unwrap()).unwrap(); - assert_eq!(parsed_gui_state, gui_state); + let parsed_builder_state: OrderBuilderStateV1 = + serde_json::from_str(&order.order_builder_state().unwrap()).unwrap(); + assert_eq!(parsed_builder_state, builder_state); } #[tokio::test] async fn from_local_db_order_populates_parsed_meta() { let source = sample_dotrain_source(); - let gui_state = sample_dotrain_gui_state(&source); + let builder_state = sample_order_builder_state(&source); let meta_hex = encode_meta_items_hex(vec![ RainMetaDocumentV1Item::from(source.clone()), - RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(), ]); let meta_bytes = alloy::hex::decode(meta_hex.strip_prefix("0x").unwrap_or(&meta_hex)).unwrap(); @@ -1761,18 +1761,18 @@ mod tests { assert_eq!(order.parsed_meta().len(), 2); assert_eq!(order.dotrain_source(), Some(source.0)); - let parsed_gui_state: DotrainGuiStateV1 = - serde_json::from_str(&order.dotrain_gui_state().unwrap()).unwrap(); - assert_eq!(parsed_gui_state, gui_state); + let parsed_builder_state: OrderBuilderStateV1 = + serde_json::from_str(&order.order_builder_state().unwrap()).unwrap(); + assert_eq!(parsed_builder_state, builder_state); } #[tokio::test] async fn fetch_dotrain_source_skips_when_source_already_present() { let source = sample_dotrain_source(); - let gui_state = sample_dotrain_gui_state(&source); + let builder_state = sample_order_builder_state(&source); let meta_hex = encode_meta_items_hex(vec![ RainMetaDocumentV1Item::from(source.clone()), - RainMetaDocumentV1Item::try_from(gui_state).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state).unwrap(), ]); let mut sg_order = get_order1(); @@ -1798,7 +1798,7 @@ mod tests { } #[tokio::test] - async fn fetch_dotrain_source_returns_ok_without_gui_state() { + async fn fetch_dotrain_source_returns_ok_without_builder_state() { let mut sg_order = get_order1(); sg_order.meta = None; @@ -1824,7 +1824,7 @@ mod tests { #[tokio::test] async fn fetch_dotrain_source_adds_source_from_metaboard() { let source = sample_dotrain_source(); - let gui_state = sample_dotrain_gui_state(&source); + let builder_state = sample_order_builder_state(&source); let rainlang_meta = RainMetaDocumentV1Item { payload: ByteBuf::from("sample rainlang source".as_bytes()), magic: KnownMagic::RainlangSourceV1, @@ -1834,7 +1834,7 @@ mod tests { }; let order_meta_hex = encode_meta_items_hex(vec![ rainlang_meta, - RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(), ]); let server = MockServer::start_async().await; @@ -1856,7 +1856,7 @@ mod tests { "sender": "0x0000000000000000000000000000000000000000", "id": "0x01", "metaBoard": { "address": "0x0000000000000000000000000000000000000000" }, - "subject": gui_state.dotrain_hash().to_string() + "subject": builder_state.dotrain_hash().to_string() } ] } @@ -1876,15 +1876,15 @@ mod tests { assert_eq!(order.parsed_meta().len(), 2); assert_eq!(order.dotrain_source(), Some(source.0)); - let parsed_gui_state: DotrainGuiStateV1 = - serde_json::from_str(&order.dotrain_gui_state().unwrap()).unwrap(); - assert_eq!(parsed_gui_state, gui_state); + let parsed_builder_state: OrderBuilderStateV1 = + serde_json::from_str(&order.order_builder_state().unwrap()).unwrap(); + assert_eq!(parsed_builder_state, builder_state); } #[tokio::test] async fn fetch_dotrain_source_handles_invalid_meta_bytes() { let source = sample_dotrain_source(); - let gui_state = sample_dotrain_gui_state(&source); + let builder_state = sample_order_builder_state(&source); let rainlang_meta = RainMetaDocumentV1Item { payload: ByteBuf::from("sample rainlang source".as_bytes()), magic: KnownMagic::RainlangSourceV1, @@ -1894,7 +1894,7 @@ mod tests { }; let meta_hex = encode_meta_items_hex(vec![ rainlang_meta, - RainMetaDocumentV1Item::try_from(gui_state.clone()).unwrap(), + RainMetaDocumentV1Item::try_from(builder_state.clone()).unwrap(), ]); let server = MockServer::start_async().await; @@ -1910,7 +1910,7 @@ mod tests { "sender": "0x0000000000000000000000000000000000000000", "id": "0x01", "metaBoard": { "address": "0x0000000000000000000000000000000000000000" }, - "subject": gui_state.dotrain_hash().to_string() + "subject": builder_state.dotrain_hash().to_string() } ] } @@ -1929,9 +1929,9 @@ mod tests { order.fetch_dotrain_source().await.unwrap(); assert_eq!(order.parsed_meta().len(), 1); - let parsed_gui_state: DotrainGuiStateV1 = - serde_json::from_str(&order.dotrain_gui_state().unwrap()).unwrap(); - assert_eq!(parsed_gui_state, gui_state); + let parsed_builder_state: OrderBuilderStateV1 = + serde_json::from_str(&order.order_builder_state().unwrap()).unwrap(); + assert_eq!(parsed_builder_state, builder_state); } #[derive(Clone)] diff --git a/crates/common/src/raindex_order_builder/deposits.rs b/crates/common/src/raindex_order_builder/deposits.rs new file mode 100644 index 0000000000..a5f76d1e82 --- /dev/null +++ b/crates/common/src/raindex_order_builder/deposits.rs @@ -0,0 +1,579 @@ +use super::*; +use rain_orderbook_app_settings::order_builder::OrderBuilderDepositCfg; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct TokenDeposit { + pub token: String, + pub amount: String, + pub address: Address, +} + +impl RaindexOrderBuilder { + pub fn check_deposits(&self) -> Result<(), RaindexOrderBuilderError> { + let deployment = self.get_current_deployment()?; + + for deposit in deployment.deposits.iter() { + if deposit.token.is_none() { + return Err(RaindexOrderBuilderError::MissingDepositToken( + deployment.key.clone(), + )); + } + + let token = deposit.token.as_ref().unwrap(); + if !self.deposits.contains_key(&token.key) { + return Err(RaindexOrderBuilderError::DepositNotSet( + token + .symbol + .clone() + .unwrap_or(token.label.clone().unwrap_or(token.key.clone())), + )); + } + } + Ok(()) + } + + pub fn get_deposit_config( + &self, + key: &str, + ) -> Result { + let deployment = self.get_current_deployment()?; + let deposit_config = deployment + .deposits + .iter() + .find(|dg| dg.token.as_ref().is_some_and(|t| t.key == *key)) + .ok_or(RaindexOrderBuilderError::DepositTokenNotFound( + key.to_string(), + ))?; + Ok(deposit_config.clone()) + } + + pub fn get_deposits(&self) -> Result, RaindexOrderBuilderError> { + self.deposits + .iter() + .map(|(key, value)| { + let deposit_config = self.get_deposit_config(key)?; + let amount: String = if value.is_preset { + let index = value + .value + .parse::() + .map_err(|_| RaindexOrderBuilderError::InvalidPreset)?; + deposit_config + .presets + .as_ref() + .ok_or(RaindexOrderBuilderError::PresetsNotSet)? + .get(index) + .ok_or(RaindexOrderBuilderError::InvalidPreset)? + .clone() + } else { + value.value.clone() + }; + + if deposit_config.token.is_none() { + return Err(RaindexOrderBuilderError::TokenMustBeSelected(key.clone())); + } + let token = deposit_config.token.as_ref().unwrap(); + + Ok(TokenDeposit { + token: token.key.clone(), + amount, + address: token.address, + }) + }) + .collect::, RaindexOrderBuilderError>>() + } + + pub async fn set_deposit( + &mut self, + token: String, + amount: String, + ) -> Result<(), RaindexOrderBuilderError> { + let deposit_config = self.get_deposit_config(&token)?; + + if amount.is_empty() { + return Err(RaindexOrderBuilderError::DepositAmountCannotBeEmpty); + } + + if let Some(validation) = &deposit_config.validation { + let token_info = self.get_token_info(token.clone()).await?; + validation::validate_deposit_amount(&token_info.name, &amount, validation)?; + } + + let value = match deposit_config.presets.as_ref() { + Some(presets) => match presets.iter().position(|p| **p == amount) { + Some(index) => field_values::PairValue { + is_preset: true, + value: index.to_string(), + }, + None => field_values::PairValue { + is_preset: false, + value: amount, + }, + }, + None => field_values::PairValue { + is_preset: false, + value: amount, + }, + }; + + self.deposits.insert(token, value); + + Ok(()) + } + + pub fn unset_deposit(&mut self, token: String) -> Result<(), RaindexOrderBuilderError> { + self.deposits.remove(&token); + Ok(()) + } + + pub fn get_deposit_presets( + &self, + key: String, + ) -> Result, RaindexOrderBuilderError> { + let deposit_config = self.get_deposit_config(&key)?; + Ok(deposit_config.presets.clone().unwrap_or(vec![])) + } + + pub fn get_missing_deposits(&self) -> Result, RaindexOrderBuilderError> { + let deployment = self.get_current_deployment()?; + let mut missing_deposits = Vec::new(); + + for deposit in deployment.deposits.iter() { + if let Some(token) = &deposit.token { + if !self.deposits.contains_key(&token.key) { + missing_deposits.push(token.key.clone()); + } + } + } + Ok(missing_deposits) + } + + pub fn has_any_deposit(&self) -> Result { + Ok(!self.deposits.is_empty()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::raindex_order_builder::tests::{ + initialize_builder, initialize_builder_with_select_tokens, initialize_validation_builder, + }; + use crate::raindex_order_builder::validation; + use std::str::FromStr; + + #[tokio::test] + async fn test_get_deposit_config() { + let builder = initialize_builder(None).await; + + let deposit = builder.get_deposit_config("token1").unwrap(); + assert_eq!(deposit.token.unwrap().key, "token1"); + assert_eq!( + deposit.presets, + Some(vec![ + "0".to_string(), + "10".to_string(), + "100".to_string(), + "1000".to_string(), + "10000".to_string() + ]) + ); + + let err = builder.get_deposit_config("token2").unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DepositTokenNotFound("token2".to_string()).to_string() + ); + assert_eq!( + err.to_readable_msg(), + "The deposit token 'token2' was not found in the YAML configuration." + ); + + let builder = initialize_builder_with_select_tokens().await; + let err = builder.get_deposit_config("token3").unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DepositTokenNotFound("token3".to_string()).to_string() + ); + } + + #[tokio::test] + async fn test_get_deposits() { + let mut builder = initialize_builder(None).await; + + builder + .set_deposit("token1".to_string(), "999".to_string()) + .await + .unwrap(); + + let deposit = builder.get_deposits().unwrap(); + assert_eq!(deposit.len(), 1); + assert_eq!(deposit[0].token, "token1"); + assert_eq!(deposit[0].amount, "999"); + assert_eq!( + deposit[0].address, + Address::from_str("0xc2132d05d31c914a87c6611c10748aeb04b58e8f").unwrap() + ); + } + + #[tokio::test] + async fn test_set_deposit() { + let mut builder = initialize_builder(None).await; + + builder + .set_deposit("token1".to_string(), "999".to_string()) + .await + .unwrap(); + + let deposit = builder.get_deposits().unwrap(); + assert_eq!(deposit.len(), 1); + assert_eq!(deposit[0].token, "token1"); + assert_eq!(deposit[0].amount, "999"); + assert_eq!( + deposit[0].address, + Address::from_str("0xc2132d05d31c914a87c6611c10748aeb04b58e8f").unwrap() + ); + + let err = builder + .set_deposit("token1".to_string(), "".to_string()) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DepositAmountCannotBeEmpty.to_string() + ); + assert_eq!( + err.to_readable_msg(), + "The deposit amount cannot be an empty string. Please set a valid amount." + ); + + let mut builder = initialize_builder_with_select_tokens().await; + let err = builder + .set_deposit("token3".to_string(), "999".to_string()) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DepositTokenNotFound("token3".to_string()).to_string() + ); + } + + #[tokio::test] + async fn test_unset_deposit() { + let mut builder = initialize_builder(None).await; + + builder + .set_deposit("token1".to_string(), "999".to_string()) + .await + .unwrap(); + let deposit = builder.get_deposits().unwrap(); + assert_eq!(deposit.len(), 1); + + builder.unset_deposit("token1".to_string()).unwrap(); + assert_eq!(builder.get_deposits().unwrap().len(), 0); + } + + #[tokio::test] + async fn test_get_deposit_presets() { + let builder = initialize_builder(None).await; + + let presets = builder.get_deposit_presets("token1".to_string()).unwrap(); + assert_eq!( + presets, + vec![ + "0".to_string(), + "10".to_string(), + "100".to_string(), + "1000".to_string(), + "10000".to_string() + ] + ); + } + + #[tokio::test] + async fn test_get_missing_deposits() { + let builder = initialize_builder(None).await; + + let missing_deposits = builder.get_missing_deposits().unwrap(); + assert_eq!(missing_deposits, vec!["token1".to_string()]); + } + + #[tokio::test] + async fn test_has_any_deposit() { + let mut builder = initialize_builder(None).await; + + let has_any_deposit = builder.has_any_deposit().unwrap(); + assert!(!has_any_deposit); + + builder + .set_deposit("token1".to_string(), "999".to_string()) + .await + .unwrap(); + let has_any_deposit = builder.has_any_deposit().unwrap(); + assert!(has_any_deposit); + } + + #[tokio::test] + async fn test_check_deposits() { + let mut builder = initialize_builder(None).await; + + builder + .set_deposit("token1".to_string(), "999".to_string()) + .await + .unwrap(); + builder.check_deposits().unwrap(); + builder.unset_deposit("token1".to_string()).unwrap(); + + let err = builder.check_deposits().unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DepositNotSet("T1".to_string()).to_string() + ); + assert_eq!( + err.to_readable_msg(), + "A deposit for token 'T1' is required but has not been set." + ); + + let builder = initialize_builder_with_select_tokens().await; + let err = builder.check_deposits().unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::MissingDepositToken("select-token-deployment".to_string()) + .to_string() + ); + } + + #[tokio::test] + async fn test_save_deposit_minimum_validation() { + let mut builder = initialize_validation_builder().await; + let result = builder + .set_deposit("token1".to_string(), "50".to_string()) + .await; + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::BelowMinimum { + name, + value, + minimum, + }, + )) => { + assert_eq!(name, "Token 1"); + assert_eq!(value, "50"); + assert_eq!(minimum, "100"); + } + _ => panic!("Expected BelowMinimum error"), + } + let result = builder + .set_deposit("token1".to_string(), "100".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token1".to_string(), "500".to_string()) + .await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_save_deposit_maximum_validation() { + let mut builder = initialize_validation_builder().await; + let result = builder + .set_deposit("token2".to_string(), "5000".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token2".to_string(), "10000".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token2".to_string(), "15000".to_string()) + .await; + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::AboveMaximum { + name, + value, + maximum, + }, + )) => { + assert_eq!(name, "Token 2"); + assert_eq!(value, "15000"); + assert_eq!(maximum, "10000"); + } + _ => panic!("Expected AboveMaximum error"), + } + } + + #[tokio::test] + async fn test_save_deposit_no_validation() { + let mut builder = initialize_validation_builder().await; + let result = builder + .set_deposit("token6".to_string(), "0".to_string()) + .await; + assert!(result.is_ok()); + + let result = builder + .set_deposit("token6".to_string(), "123456789.123456789".to_string()) + .await; + assert!(result.is_ok()); + + let result = builder + .set_deposit("token6".to_string(), "0.00000001".to_string()) + .await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_save_deposit_exclusive_bounds() { + let mut builder = initialize_validation_builder().await; + let result = builder + .set_deposit("token3".to_string(), "0".to_string()) + .await; + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::BelowExclusiveMinimum { + name, + value, + exclusive_minimum, + }, + )) => { + assert_eq!(name, "Token 3"); + assert_eq!(value, "0"); + assert_eq!(exclusive_minimum, "0"); + } + _ => panic!("Expected BelowExclusiveMinimum error"), + } + let result = builder + .set_deposit("token3".to_string(), "0.001".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token3".to_string(), "49999.999".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token3".to_string(), "50000".to_string()) + .await; + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::AboveExclusiveMaximum { + name, + value, + exclusive_maximum, + }, + )) => { + assert_eq!(name, "Token 3"); + assert_eq!(value, "50000"); + assert_eq!(exclusive_maximum, "50000"); + } + _ => panic!("Expected AboveExclusiveMaximum error"), + } + } + + #[tokio::test] + async fn test_save_deposit_multiple_constraints() { + let mut builder = initialize_validation_builder().await; + + let result = builder + .set_deposit("token4".to_string(), "50".to_string()) + .await; + assert!(result.is_ok()); + + let result = builder + .set_deposit("token4".to_string(), "5".to_string()) + .await; + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::BelowMinimum { .. } + )) + )); + + let result = builder + .set_deposit("token4".to_string(), "1005".to_string()) + .await; + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::AboveMaximum { .. } + )) + )); + + let result = builder + .set_deposit("token4".to_string(), "10".to_string()) + .await; + assert!(result.is_ok()); + + let result = builder + .set_deposit("token4".to_string(), "1000".to_string()) + .await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_save_deposit_invalid_formats() { + let mut builder = initialize_validation_builder().await; + + let result = builder + .set_deposit("token1".to_string(), "abc".to_string()) + .await; + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::FloatError(..) + )) + )); + + let result = builder + .set_deposit("token1".to_string(), "12.34.56".to_string()) + .await; + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::FloatError(..) + )) + )); + + let result = builder + .set_deposit("token1".to_string(), "12,345".to_string()) + .await; + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::FloatError(..) + )) + )); + } + + #[tokio::test] + async fn test_save_deposit_edge_cases() { + let mut builder = initialize_validation_builder().await; + let result = builder + .set_deposit("token3".to_string(), "0.001".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token2".to_string(), "9999.999999999".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token1".to_string(), "100.00000".to_string()) + .await; + assert!(result.is_ok()); + let result = builder + .set_deposit("token1".to_string(), "00100".to_string()) + .await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_save_deposit_with_presets_and_validation() { + let mut builder = initialize_validation_builder().await; + let result = builder + .set_deposit("token1".to_string(), "200".to_string()) + .await; + assert!(result.is_ok()); + let deposits = builder.get_deposits().unwrap(); + assert_eq!(deposits.len(), 1); + assert_eq!(deposits[0].token, "token1"); + assert_eq!(deposits[0].amount, "200"); + } +} diff --git a/crates/common/src/raindex_order_builder/field_values.rs b/crates/common/src/raindex_order_builder/field_values.rs new file mode 100644 index 0000000000..7e8bf3d888 --- /dev/null +++ b/crates/common/src/raindex_order_builder/field_values.rs @@ -0,0 +1,810 @@ +use super::*; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct FieldValuePair { + pub field: String, + pub value: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PairValue { + pub is_preset: bool, + pub value: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct FieldValue { + pub field: String, + pub value: String, + pub is_preset: bool, +} + +impl RaindexOrderBuilder { + pub fn check_field_values(&mut self) -> Result<(), RaindexOrderBuilderError> { + let deployment = self.get_current_deployment()?; + + for field in deployment.fields.iter() { + if self.field_values.contains_key(&field.binding) { + continue; + } + + match &field.default { + Some(default_value) => { + self.set_field_value(field.binding.clone(), default_value.clone())?; + } + None => { + return Err(RaindexOrderBuilderError::FieldValueNotSet( + field.name.clone(), + )) + } + } + } + Ok(()) + } + + pub fn set_field_value( + &mut self, + field: String, + value: String, + ) -> Result<(), RaindexOrderBuilderError> { + let field_definition = self.get_field_definition(&field)?; + + if let Some(validation) = &field_definition.validation { + validation::validate_field_value(&field_definition.name, &value, validation)?; + } + + let value = match field_definition.presets.as_ref() { + Some(presets) => match presets.iter().position(|p| p.value == value) { + Some(index) => field_values::PairValue { + is_preset: true, + value: index.to_string(), + }, + None => field_values::PairValue { + is_preset: false, + value, + }, + }, + None => field_values::PairValue { + is_preset: false, + value, + }, + }; + + self.field_values.insert(field, value); + + Ok(()) + } + + pub fn set_field_values( + &mut self, + field_values: Vec, + ) -> Result<(), RaindexOrderBuilderError> { + for field_value in field_values { + self.set_field_value(field_value.field, field_value.value)?; + } + Ok(()) + } + + pub fn unset_field_value(&mut self, field: String) -> Result<(), RaindexOrderBuilderError> { + self.field_values.remove(&field); + Ok(()) + } + + pub fn get_field_value(&self, field: String) -> Result { + let field_value = + self.field_values + .get(&field) + .ok_or(RaindexOrderBuilderError::FieldBindingNotFound( + field.clone(), + ))?; + let preset = match field_value.is_preset { + true => { + let field_definition = self.get_field_definition(&field)?; + let presets = field_definition + .presets + .ok_or(RaindexOrderBuilderError::BindingHasNoPresets(field.clone()))?; + presets + .iter() + .find(|preset| preset.id == field_value.value) + .ok_or(RaindexOrderBuilderError::InvalidPreset)? + .clone() + } + false => OrderBuilderPresetCfg { + id: "".to_string(), + name: None, + value: field_value.value.clone(), + }, + }; + Ok(FieldValue { + field: field.clone(), + value: preset.value.clone(), + is_preset: field_value.is_preset, + }) + } + + pub fn get_all_field_values(&self) -> Result, RaindexOrderBuilderError> { + let mut result = Vec::new(); + for (binding, _) in self.field_values.iter() { + let field_value = self.get_field_value(binding.clone())?; + result.push(field_value); + } + Ok(result) + } + + pub fn get_field_definition( + &self, + field: &str, + ) -> Result { + let deployment = self.get_current_deployment()?; + let field_definition = deployment + .fields + .iter() + .find(|field_definition| field_definition.binding == field) + .ok_or(RaindexOrderBuilderError::FieldBindingNotFound( + field.to_string(), + ))?; + Ok(field_definition.clone()) + } + + pub fn get_all_field_definitions( + &self, + filter_defaults: Option, + ) -> Result, RaindexOrderBuilderError> { + let deployment = self.get_current_deployment()?; + let mut field_definitions = deployment.fields.clone(); + + match filter_defaults { + Some(true) => field_definitions.retain(|field| field.default.is_some()), + Some(false) => field_definitions.retain(|field| field.default.is_none()), + None => (), + } + + field_definitions.sort_by(|a, b| a.binding.cmp(&b.binding)); + + Ok(field_definitions) + } + + pub fn get_missing_field_values( + &self, + ) -> Result, RaindexOrderBuilderError> { + let deployment = self.get_current_deployment()?; + let mut missing_field_values = Vec::new(); + + for field in deployment.fields.iter() { + if !self.field_values.contains_key(&field.binding) { + missing_field_values.push(field.clone()); + } + } + Ok(missing_field_values) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::raindex_order_builder::tests::{initialize_builder, initialize_validation_builder}; + use crate::raindex_order_builder::validation; + + #[tokio::test] + async fn test_set_get_field_value() { + let mut builder = initialize_builder(None).await; + + builder + .set_field_value("binding-1".to_string(), "some-default-value".to_string()) + .unwrap(); + builder + .set_field_value("binding-2".to_string(), "99.2".to_string()) + .unwrap(); + + let field_value = builder.get_field_value("binding-1".to_string()).unwrap(); + assert_eq!(field_value.field, "binding-1"); + assert_eq!(field_value.value, "some-default-value"); + assert!(!field_value.is_preset); + + let field_value = builder.get_field_value("binding-2".to_string()).unwrap(); + assert_eq!(field_value.field, "binding-2"); + assert_eq!(field_value.value, "99.2"); + assert!(field_value.is_preset); + } + + #[tokio::test] + async fn test_set_get_all_field_values() { + let mut builder = initialize_builder(None).await; + + builder + .set_field_values(vec![ + FieldValuePair { + field: "binding-1".to_string(), + value: "some-default-value".to_string(), + }, + FieldValuePair { + field: "binding-2".to_string(), + value: "99.2".to_string(), + }, + ]) + .unwrap(); + + let field_values = builder.get_all_field_values().unwrap(); + assert_eq!(field_values.len(), 2); + assert_eq!(field_values[0].field, "binding-1"); + assert_eq!(field_values[0].value, "some-default-value"); + assert!(!field_values[0].is_preset); + assert_eq!(field_values[1].field, "binding-2"); + assert_eq!(field_values[1].value, "99.2"); + assert!(field_values[1].is_preset); + } + + fn get_binding_1() -> OrderBuilderFieldDefinitionCfg { + OrderBuilderFieldDefinitionCfg { + binding: String::from("binding-1"), + name: String::from("Field 1 name"), + description: Some(String::from("Field 1 description")), + presets: Some(Vec::from([ + OrderBuilderPresetCfg { + id: String::from("0"), + name: Some(String::from("Preset 1")), + value: String::from("0x1234567890abcdef1234567890abcdef12345678"), + }, + OrderBuilderPresetCfg { + id: String::from("1"), + name: Some(String::from("Preset 2")), + value: String::from("false"), + }, + OrderBuilderPresetCfg { + id: String::from("2"), + name: Some(String::from("Preset 3")), + value: String::from("some-string"), + }, + ])), + default: Some(String::from("some-default-value")), + show_custom_field: None, + validation: None, + } + } + + fn get_binding_2() -> OrderBuilderFieldDefinitionCfg { + OrderBuilderFieldDefinitionCfg { + binding: String::from("binding-2"), + name: String::from("Field 2 name"), + description: Some(String::from("Field 2 description")), + presets: Some(Vec::from([ + OrderBuilderPresetCfg { + id: String::from("0"), + name: None, + value: String::from("99.2"), + }, + OrderBuilderPresetCfg { + id: String::from("1"), + name: None, + value: String::from("582.1"), + }, + OrderBuilderPresetCfg { + id: String::from("2"), + name: None, + value: String::from("648.239"), + }, + ])), + default: None, + show_custom_field: Some(true), + validation: None, + } + } + + #[tokio::test] + async fn test_unset_field_value() { + let mut builder = initialize_builder(None).await; + + builder + .set_field_value("binding-1".to_string(), "some-default-value".to_string()) + .unwrap(); + builder.get_field_value("binding-1".to_string()).unwrap(); + + builder.unset_field_value("binding-1".to_string()).unwrap(); + let err = builder + .get_field_value("binding-1".to_string()) + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::FieldBindingNotFound("binding-1".to_string()).to_string() + ); + } + + #[tokio::test] + async fn test_get_field_definition() { + let builder = initialize_builder(None).await; + + let field_definition = builder.get_field_definition("binding-1").unwrap(); + assert_eq!(field_definition, get_binding_1()); + + let field_definition = builder.get_field_definition("binding-2").unwrap(); + assert_eq!(field_definition, get_binding_2()); + + let err = builder.get_field_definition("invalid-binding").unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::FieldBindingNotFound("invalid-binding".to_string()) + .to_string() + ); + assert_eq!( + err.to_readable_msg(), + "The field binding 'invalid-binding' could not be found in the YAML configuration." + ); + } + + #[tokio::test] + async fn test_get_all_field_definitions() { + let builder = initialize_builder(None).await; + + let field_definitions = builder.get_all_field_definitions(None).unwrap(); + assert_eq!(field_definitions.len(), 2); + assert_eq!(field_definitions[0], get_binding_1()); + assert_eq!(field_definitions[1], get_binding_2()); + + let field_definitions = builder.get_all_field_definitions(Some(true)).unwrap(); + assert_eq!(field_definitions.len(), 1); + assert_eq!(field_definitions[0], get_binding_1()); + + let field_definitions = builder.get_all_field_definitions(Some(false)).unwrap(); + assert_eq!(field_definitions.len(), 1); + assert_eq!(field_definitions[0], get_binding_2()); + } + + #[tokio::test] + async fn test_get_missing_field_values() { + let mut builder = initialize_builder(None).await; + + let field_values = builder.get_missing_field_values().unwrap(); + assert_eq!(field_values.len(), 2); + assert_eq!(field_values[0], get_binding_1()); + assert_eq!(field_values[1], get_binding_2()); + + builder + .set_field_value("binding-1".to_string(), "some-default-value".to_string()) + .unwrap(); + + let field_values = builder.get_missing_field_values().unwrap(); + assert_eq!(field_values.len(), 1); + assert_eq!(field_values[0], get_binding_2()); + } + + #[tokio::test] + async fn test_check_field_values() { + let mut builder = initialize_builder(None).await; + + let err = builder.check_field_values().unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::FieldValueNotSet("Field 2 name".to_string()).to_string() + ); + + builder + .set_field_value("binding-2".to_string(), "99.2".to_string()) + .unwrap(); + let res = builder.check_field_values(); + assert!(res.is_ok()); + } + + #[tokio::test] + async fn test_set_field_value_number_minimum_maximum() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("price-field".to_string(), "50.00".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("price-field".to_string(), "5.00".to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::BelowMinimum { + name, + value, + minimum, + }, + )) => { + assert_eq!(name, "Price Field"); + assert_eq!(value, "5.00"); + assert_eq!(minimum, "10"); + } + _ => panic!("Expected BelowMinimum error"), + } + + let result = builder.set_field_value("price-field".to_string(), "1500.00".to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::AboveMaximum { + name, + value, + maximum, + }, + )) => { + assert_eq!(name, "Price Field"); + assert_eq!(value, "1500.00"); + assert_eq!(maximum, "1000"); + } + _ => panic!("Expected AboveMaximum error"), + } + + let result = builder.set_field_value("price-field".to_string(), "10".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("price-field".to_string(), "1000".to_string()); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_set_field_value_number_exclusive_bounds() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("quantity-field".to_string(), "0".to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::BelowExclusiveMinimum { + name, + value, + exclusive_minimum, + }, + )) => { + assert_eq!(name, "Quantity Field"); + assert_eq!(value, "0"); + assert_eq!(exclusive_minimum, "0"); + } + _ => panic!("Expected BelowExclusiveMinimum error"), + } + + let result = builder.set_field_value("quantity-field".to_string(), "0.001".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("quantity-field".to_string(), "100000".to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::AboveExclusiveMaximum { + name, + value, + exclusive_maximum, + }, + )) => { + assert_eq!(name, "Quantity Field"); + assert_eq!(value, "100000"); + assert_eq!(exclusive_maximum, "100000"); + } + _ => panic!("Expected AboveExclusiveMaximum error"), + } + + let result = builder.set_field_value("quantity-field".to_string(), "99999.999".to_string()); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_set_field_value_boolean() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("enabled-field".to_string(), "true".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("enabled-field".to_string(), "false".to_string()); + assert!(result.is_ok()); + + let test_cases = vec![ + "True", "FALSE", "yes", "no", "on", "off", "", " true", "true ", "1", "0", + ]; + + for test_value in test_cases { + let result = + builder.set_field_value("enabled-field".to_string(), test_value.to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::InvalidBoolean { name, value }, + )) => { + assert_eq!(name, "Enabled"); + assert_eq!(value, test_value); + } + _ => panic!("Expected InvalidBoolean error for value: {}", test_value), + } + } + } + + #[tokio::test] + async fn test_set_field_value_preset_with_validation() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("preset-number-field".to_string(), "100".to_string()); + assert!(result.is_ok()); + let field_value = builder + .get_field_value("preset-number-field".to_string()) + .unwrap(); + assert!(field_value.is_preset); + assert_eq!(field_value.value, "100"); + + let result = builder.set_field_value("preset-number-field".to_string(), "120".to_string()); + assert!(result.is_ok()); + let field_value = builder + .get_field_value("preset-number-field".to_string()) + .unwrap(); + assert!(!field_value.is_preset); + assert_eq!(field_value.value, "120"); + + let result = builder.set_field_value("preset-number-field".to_string(), "5".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::BelowMinimum { .. } + )) + )); + } + + #[tokio::test] + async fn test_set_field_value_no_validation() { + let mut builder = initialize_validation_builder().await; + + let test_values = vec![ + "123", + "abc", + "true", + "false", + "", + "!@#$%^&*()", + "very long string with many characters", + "0", + "🦀 Rust 🦀", + ]; + + for value in test_values { + let result = + builder.set_field_value("no-validation-field".to_string(), value.to_string()); + assert!(result.is_ok(), "Failed to save value: {}", value); + + let field_value = builder + .get_field_value("no-validation-field".to_string()) + .unwrap(); + assert_eq!(field_value.value, value); + } + } + + #[tokio::test] + async fn test_set_field_value_number_complex_constraints() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("percentage-field".to_string(), "50.5".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("percentage-field".to_string(), "0".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("percentage-field".to_string(), "100".to_string()); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_set_field_value_number_invalid_formats() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("simple-number".to_string(), "".to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::InvalidNumber { name, value }, + )) => { + assert_eq!(name, "Simple Number"); + assert_eq!(value, ""); + } + _ => panic!("Expected InvalidNumber error"), + } + + let result = builder.set_field_value("simple-number".to_string(), "abc".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::FloatError(..) + )) + )); + + let result = builder.set_field_value("simple-number".to_string(), "12.34.56".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::FloatError(..) + )) + )); + + let result = builder.set_field_value("simple-number".to_string(), "١٢٣".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::FloatError(..) + )) + )); + } + + #[tokio::test] + async fn test_set_field_value_string_length_constraints() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("username-field".to_string(), "john_doe".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("username-field".to_string(), "jo".to_string()); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooShort { + name, + length, + minimum, + }, + )) => { + assert_eq!(name, "Username"); + assert_eq!(length, 2); + assert_eq!(minimum, 3); + } + _ => panic!("Expected StringTooShort error"), + } + + let result = builder.set_field_value( + "username-field".to_string(), + "this_username_is_way_too_long".to_string(), + ); + match result { + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooLong { + name, + length, + maximum, + }, + )) => { + assert_eq!(name, "Username"); + assert_eq!(length, 29); + assert_eq!(maximum, 20); + } + _ => panic!("Expected StringTooLong error"), + } + + let result = builder.set_field_value("username-field".to_string(), "abc".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("username-field".to_string(), "a".repeat(20)); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_set_field_value_string_edge_cases() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value("description-field".to_string(), "".to_string()); + assert!(result.is_ok()); + + let result = builder.set_field_value("code-field".to_string(), "".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooShort { .. } + )) + )); + + let result = builder.set_field_value("description-field".to_string(), "a".repeat(500)); + assert!(result.is_ok()); + + let result = builder.set_field_value("description-field".to_string(), "a".repeat(501)); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooLong { .. } + )) + )); + + let result = + builder.set_field_value("username-field".to_string(), "🦀🦀🦀rust🦀🦀🦀".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooLong { .. } + )) + )); + + let result = builder.set_field_value( + "any-string".to_string(), + "Any value at all!@#$%^&*()".to_string(), + ); + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_set_field_value_string_preset_with_validation() { + let mut builder = initialize_validation_builder().await; + + let result = + builder.set_field_value("preset-string-field".to_string(), "alpha".to_string()); + assert!(result.is_ok()); + let field_value = builder + .get_field_value("preset-string-field".to_string()) + .unwrap(); + assert!(field_value.is_preset); + + let result = + builder.set_field_value("preset-string-field".to_string(), "custom".to_string()); + assert!(result.is_ok()); + let field_value = builder + .get_field_value("preset-string-field".to_string()) + .unwrap(); + assert!(!field_value.is_preset); + + let result = builder.set_field_value("preset-string-field".to_string(), "xyz".to_string()); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooShort { .. } + )) + )); + + let result = builder.set_field_value( + "preset-string-field".to_string(), + "verylongvalue".to_string(), + ); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooLong { .. } + )) + )); + } + + #[tokio::test] + async fn test_set_field_values_batch_with_validation() { + let mut builder = initialize_validation_builder().await; + + let valid_batch = vec![ + FieldValuePair { + field: "price-field".to_string(), + value: "100.00".to_string(), + }, + FieldValuePair { + field: "username-field".to_string(), + value: "valid_user".to_string(), + }, + FieldValuePair { + field: "enabled-field".to_string(), + value: "true".to_string(), + }, + ]; + + let result = builder.set_field_values(valid_batch); + assert!(result.is_ok()); + + let invalid_batch = vec![ + FieldValuePair { + field: "price-field".to_string(), + value: "100.00".to_string(), + }, + FieldValuePair { + field: "username-field".to_string(), + value: "ab".to_string(), + }, + FieldValuePair { + field: "enabled-field".to_string(), + value: "true".to_string(), + }, + ]; + + let result = builder.set_field_values(invalid_batch); + assert!(matches!( + result, + Err(RaindexOrderBuilderError::ValidationError( + validation::BuilderValidationError::StringTooShort { .. } + )) + )); + + let field_result = builder.get_field_value("price-field".to_string()); + assert!(field_result.is_ok()); + } + + #[tokio::test] + async fn test_very_precise_decimal_validation() { + let mut builder = initialize_validation_builder().await; + + let result = builder.set_field_value( + "simple-number".to_string(), + "0.123456789012345678".to_string(), + ); + assert!(result.is_ok()); + + let result = builder.set_field_value("price-field".to_string(), "999.99".to_string()); + assert!(result.is_ok()); + } +} diff --git a/crates/common/src/raindex_order_builder/mod.rs b/crates/common/src/raindex_order_builder/mod.rs new file mode 100644 index 0000000000..edf1476e70 --- /dev/null +++ b/crates/common/src/raindex_order_builder/mod.rs @@ -0,0 +1,1152 @@ +pub use crate::erc20::ExtendedTokenInfo; +use crate::{ + dotrain::{types::patterns::FRONTMATTER_SEPARATOR, RainDocument}, + dotrain_order::{DotrainOrder, DotrainOrderError}, + erc20::ERC20, + utils::amount_formatter::AmountFormatterError, +}; +use alloy::primitives::Address; +use alloy_ethers_typecast::ReadableClientError; +use base64::{engine::general_purpose::URL_SAFE, Engine}; +use flate2::{read::GzDecoder, write::GzEncoder, Compression}; +use rain_math_float::FloatError; +use rain_metaboard_subgraph::metaboard_client::MetaboardSubgraphClientError; +use rain_orderbook_app_settings::{ + deployment::DeploymentCfg, + order::OrderCfg, + order_builder::{ + NameAndDescriptionCfg, OrderBuilderCfg, OrderBuilderDeploymentCfg, + OrderBuilderFieldDefinitionCfg, OrderBuilderPresetCfg, ParseOrderBuilderConfigSourceError, + }, + yaml::{ + context::ContextProfile, + dotrain::{DotrainYaml, DotrainYamlValidation}, + emitter, YamlError, YamlParsable, + }, +}; +use serde::{Deserialize, Serialize}; +use std::io::prelude::*; +use std::{ + collections::BTreeMap, + sync::{Arc, RwLock}, +}; +use strict_yaml_rust::StrictYaml; +use thiserror::Error; + +pub mod deposits; +pub mod field_values; +pub mod order_operations; +pub mod select_tokens; +pub mod state_management; +pub mod validation; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct RaindexOrderBuilder { + dotrain_order: DotrainOrder, + selected_deployment: String, + field_values: BTreeMap, + deposits: BTreeMap, + dotrain_hash: String, +} +impl Default for RaindexOrderBuilder { + fn default() -> Self { + Self { + dotrain_order: DotrainOrder::dummy(), + selected_deployment: "".to_string(), + field_values: BTreeMap::new(), + deposits: BTreeMap::new(), + dotrain_hash: "".to_string(), + } + } +} + +impl RaindexOrderBuilder { + pub async fn get_deployment_keys( + dotrain: String, + settings: Option>, + ) -> Result, RaindexOrderBuilderError> { + let documents = RaindexOrderBuilder::get_yaml_documents(&dotrain, settings)?; + Ok(OrderBuilderCfg::parse_deployment_keys(documents)?) + } + + pub async fn new_with_deployment( + dotrain: String, + settings: Option>, + selected_deployment: String, + ) -> Result { + let documents = RaindexOrderBuilder::get_yaml_documents(&dotrain, settings.clone())?; + + let keys = OrderBuilderCfg::parse_deployment_keys(documents.clone())?; + if !keys.contains(&selected_deployment) { + return Err(RaindexOrderBuilderError::DeploymentNotFound( + selected_deployment.clone(), + )); + } + + let dotrain_order = DotrainOrder::create_with_profile( + dotrain.clone(), + settings, + ContextProfile::builder(selected_deployment.clone()), + ) + .await?; + + let dotrain_hash = RaindexOrderBuilder::compute_state_hash(&dotrain_order)?; + + Ok(RaindexOrderBuilder { + dotrain_order, + selected_deployment, + field_values: BTreeMap::new(), + deposits: BTreeMap::new(), + dotrain_hash, + }) + } + + pub fn get_builder_config(&self) -> Result { + if !OrderBuilderCfg::check_builder_key_exists( + self.dotrain_order.dotrain_yaml().documents.clone(), + )? { + return Err(RaindexOrderBuilderError::BuilderConfigNotFound); + } + let config = self + .dotrain_order + .dotrain_yaml() + .get_order_builder(&self.selected_deployment)? + .ok_or(RaindexOrderBuilderError::BuilderConfigNotFound)?; + Ok(config) + } + + pub fn get_current_deployment( + &self, + ) -> Result { + let config = self.get_builder_config()?; + let (_, deployment) = config + .deployments + .into_iter() + .find(|(name, _)| name == &self.selected_deployment) + .ok_or(RaindexOrderBuilderError::DeploymentNotFound( + self.selected_deployment.clone(), + ))?; + Ok(deployment.clone()) + } + + pub async fn get_token_info( + &self, + key: String, + ) -> Result { + let token = self.dotrain_order.orderbook_yaml().get_token(&key)?; + Ok(ExtendedTokenInfo::from_token_cfg(&token).await?) + } + + pub async fn get_all_token_infos( + &self, + ) -> Result, RaindexOrderBuilderError> { + let select_tokens = self.get_select_tokens()?; + + let token_keys = match select_tokens.is_empty() { + true => { + let order_key = DeploymentCfg::parse_order_key( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )?; + OrderCfg::parse_io_token_keys( + self.dotrain_order.dotrain_yaml().documents, + &order_key, + )? + } + false => select_tokens + .iter() + .map(|token| token.key.clone()) + .collect(), + }; + + let mut result = Vec::new(); + for key in token_keys.iter() { + result.push(self.get_token_info(key.clone()).await?); + } + Ok(result) + } + + pub fn get_order_details( + dotrain: String, + settings: Option>, + ) -> Result { + let documents = RaindexOrderBuilder::get_yaml_documents(&dotrain, settings)?; + Ok(OrderBuilderCfg::parse_order_details(documents)?) + } + + pub fn get_deployment_details( + dotrain: String, + settings: Option>, + ) -> Result, RaindexOrderBuilderError> { + let documents = RaindexOrderBuilder::get_yaml_documents(&dotrain, settings)?; + Ok(OrderBuilderCfg::parse_deployment_details(documents)?) + } + + pub fn get_deployment_detail( + dotrain: String, + settings: Option>, + key: String, + ) -> Result { + let deployment_details = RaindexOrderBuilder::get_deployment_details(dotrain, settings)?; + let deployment_detail = deployment_details + .get(&key) + .ok_or(RaindexOrderBuilderError::DeploymentNotFound(key))?; + Ok(deployment_detail.clone()) + } + + pub fn get_current_deployment_details( + &self, + ) -> Result { + let deployment_details = OrderBuilderCfg::parse_deployment_details( + self.dotrain_order.dotrain_yaml().documents.clone(), + )?; + Ok(deployment_details + .get(&self.selected_deployment) + .ok_or(RaindexOrderBuilderError::DeploymentNotFound( + self.selected_deployment.clone(), + ))? + .clone()) + } + + pub fn generate_dotrain_text(&self) -> Result { + let rain_document = RainDocument::create(self.dotrain_order.dotrain()?, None, None, None); + let dotrain = format!( + "{}\n{}\n{}", + emitter::emit_documents(&self.dotrain_order.dotrain_yaml().documents)?, + FRONTMATTER_SEPARATOR, + rain_document.body() + ); + Ok(dotrain) + } + + pub async fn get_composed_rainlang(&mut self) -> Result { + self.update_scenario_bindings()?; + let dotrain = self.generate_dotrain_text()?; + let deployment = self.get_current_deployment()?; + let dotrain_order = DotrainOrder::create_with_profile( + dotrain.clone(), + None, + ContextProfile::builder(deployment.deployment.key.clone()), + ) + .await?; + let rainlang = dotrain_order + .compose_deployment_to_rainlang(self.selected_deployment.clone()) + .await?; + Ok(rainlang) + } + + pub fn get_yaml_documents( + dotrain: &str, + settings: Option>, + ) -> Result>>, RaindexOrderBuilderError> { + let frontmatter = RainDocument::get_front_matter(dotrain) + .unwrap_or("") + .to_string(); + let mut sources = vec![frontmatter]; + if let Some(settings) = settings { + sources.extend(settings); + } + + let dotrain_yaml = DotrainYaml::new(sources, DotrainYamlValidation::default())?; + Ok(dotrain_yaml.documents) + } + + pub fn dotrain_order(&self) -> &DotrainOrder { + &self.dotrain_order + } + + pub fn selected_deployment(&self) -> &str { + &self.selected_deployment + } +} + +#[derive(Error, Debug)] +pub enum RaindexOrderBuilderError { + #[error("Builder config not found")] + BuilderConfigNotFound, + #[error("Deployment not found: {0}")] + DeploymentNotFound(String), + #[error("Field binding not found: {0}")] + FieldBindingNotFound(String), + #[error("Missing field value: {0}")] + FieldValueNotSet(String), + #[error("Deposit token not found in builder config: {0}")] + DepositTokenNotFound(String), + #[error("Missing deposit with token: {0}")] + DepositNotSet(String), + #[error("Missing deposit token for current deployment: {0}")] + MissingDepositToken(String), + #[error("Deposit amount cannot be an empty string")] + DepositAmountCannotBeEmpty, + #[error("Orderbook not found")] + OrderbookNotFound, + #[error("Order not found: {0}")] + OrderNotFound(String), + #[error("Deserialized dotrain mismatch")] + DotrainMismatch, + #[error("Vault id not found for output index: {0}")] + VaultIdNotFound(String), + #[error("Deployer not found")] + DeployerNotFound, + #[error("Token not found {0}")] + TokenNotFound(String), + #[error("Invalid preset")] + InvalidPreset, + #[error("Presets not set")] + PresetsNotSet, + #[error("Select tokens not set")] + SelectTokensNotSet, + #[error("Token must be selected: {0}")] + TokenMustBeSelected(String), + #[error("Binding has no presets: {0}")] + BindingHasNoPresets(String), + #[error("Token not in select tokens: {0}")] + TokenNotInSelectTokens(String), + #[error(transparent)] + DotrainOrderError(#[from] DotrainOrderError), + #[error(transparent)] + ParseOrderBuilderConfigSourceError(#[from] ParseOrderBuilderConfigSourceError), + #[error(transparent)] + IoError(#[from] std::io::Error), + #[error(transparent)] + BincodeError(#[from] bincode::Error), + #[error(transparent)] + Base64Error(#[from] base64::DecodeError), + #[error(transparent)] + FromHexError(#[from] alloy::hex::FromHexError), + #[error(transparent)] + ReadableClientError(#[from] ReadableClientError), + #[error(transparent)] + DepositError(#[from] crate::deposit::DepositError), + #[error(transparent)] + ParseError(#[from] alloy::primitives::ruint::ParseError), + #[error(transparent)] + ReadContractParametersBuilderError( + #[from] alloy_ethers_typecast::ReadContractParametersBuilderError, + ), + #[error(transparent)] + UnitsError(#[from] alloy::primitives::utils::UnitsError), + #[error(transparent)] + WritableTransactionExecuteError(#[from] crate::transaction::WritableTransactionExecuteError), + #[error(transparent)] + AddOrderArgsError(#[from] crate::add_order::AddOrderArgsError), + #[error(transparent)] + ERC20Error(#[from] crate::erc20::Error), + #[error(transparent)] + SolTypesError(#[from] alloy::sol_types::Error), + #[error(transparent)] + YamlError(#[from] YamlError), + #[error(transparent)] + ValidationError(#[from] validation::BuilderValidationError), + #[error(transparent)] + UrlParseError(#[from] url::ParseError), + #[error(transparent)] + AmountFormatterError(#[from] AmountFormatterError), + #[error(transparent)] + FloatError(#[from] FloatError), + #[error(transparent)] + RainMetadataError(#[from] rain_metadata::Error), + #[error("No address found in metaboard subgraph")] + NoAddressInMetaboardSubgraph, + #[error(transparent)] + MetaboardSubgraphClientError(#[from] MetaboardSubgraphClientError), +} + +impl RaindexOrderBuilderError { + pub fn to_readable_msg(&self) -> String { + match self { + Self::BuilderConfigNotFound => + "The builder configuration could not be found. Please check your YAML configuration file.".to_string(), + Self::DeploymentNotFound(name) => + format!("The deployment '{}' could not be found. Please select a valid deployment from your YAML configuration.", name), + Self::FieldBindingNotFound(field) => + format!("The field binding '{}' could not be found in the YAML configuration.", field), + Self::FieldValueNotSet(field) => + format!("The value for field '{}' is required but has not been set.", field), + Self::DepositTokenNotFound(token) => + format!("The deposit token '{}' was not found in the YAML configuration.", token), + Self::DepositNotSet(token) => + format!("A deposit for token '{}' is required but has not been set.", token), + Self::MissingDepositToken(deployment) => + format!("A deposit for token is required but has not been set for deployment '{}'.", deployment), + Self::DepositAmountCannotBeEmpty => + "The deposit amount cannot be an empty string. Please set a valid amount.".to_string(), + Self::OrderbookNotFound => + "The orderbook configuration could not be found. Please check your YAML configuration.".to_string(), + Self::OrderNotFound(order) => + format!("The order '{}' could not be found in the YAML configuration.", order), + Self::DotrainMismatch => + "There was a mismatch in the dotrain configuration. Please check your YAML configuration for consistency.".to_string(), + Self::VaultIdNotFound(index) => + format!("The vault ID for output index '{}' could not be found in the YAML configuration.", index), + Self::DeployerNotFound => + "The deployer configuration could not be found. Please check your YAML configuration.".to_string(), + Self::TokenNotFound(token) => + format!("The token '{}' could not be found in the YAML configuration.", token), + Self::InvalidPreset => + "The selected preset is invalid. Please choose a different preset from your YAML configuration.".to_string(), + Self::PresetsNotSet => + "No presets have been configured. Please check your YAML configuration.".to_string(), + Self::SelectTokensNotSet => + "No tokens have been configured for selection. Please check your YAML configuration.".to_string(), + Self::TokenMustBeSelected(token) => + format!("The token '{}' must be selected to proceed.", token), + Self::BindingHasNoPresets(binding) => + format!("The binding '{}' does not have any presets configured in the YAML configuration.", binding), + Self::TokenNotInSelectTokens(token) => + format!("The token '{}' is not in the list of selectable tokens defined in the YAML configuration.", token), + Self::DotrainOrderError(err) => + format!("Order configuration error in YAML: {}", err), + Self::ParseOrderBuilderConfigSourceError(err) => + format!("Failed to parse YAML builder configuration: {}", err), + Self::IoError(err) => + format!("I/O error: {}", err), + Self::BincodeError(err) => + format!("Data serialization error: {}", err), + Self::Base64Error(err) => + format!("Base64 encoding/decoding error: {}", err), + Self::FromHexError(err) => + format!("Invalid hexadecimal value: {}", err), + Self::ReadableClientError(err) => + format!("Network client error: {}", err), + Self::DepositError(err) => + format!("Deposit error: {}", err), + Self::ParseError(err) => + format!("Number parsing error: {}", err), + Self::ReadContractParametersBuilderError(err) => + format!("Contract parameter error: {}", err), + Self::UnitsError(err) => + format!("Unit conversion error: {}", err), + Self::WritableTransactionExecuteError(err) => + format!("Transaction execution error: {}", err), + Self::AddOrderArgsError(err) => + format!("Invalid order arguments: {}", err), + Self::ERC20Error(err) => + format!("ERC20 token error: {}", err), + Self::SolTypesError(err) => + format!("Solidity type error: {}", err), + Self::YamlError(err) => format!("YAML configuration error: {}", err), + Self::ValidationError(err) => format!("Validation error: {}", err), + Self::UrlParseError(err) => format!("URL parsing error: {err}"), + Self::AmountFormatterError(err) => + format!("There was a problem formatting the amount: {err}"), + Self::FloatError(err) => { + format!("There was a problem with the float value: {err}") + } + Self::RainMetadataError(err) => + format!("There was a problem with the rain metadata: {err}"), + Self::NoAddressInMetaboardSubgraph => + "No address was found in the metaboard subgraph response.".to_string(), + Self::MetaboardSubgraphClientError(err) => + format!("There was a problem with the metaboard subgraph client: {err}"), + } + } +} + +#[cfg(test)] +pub mod tests { + use super::*; + use rain_orderbook_app_settings::spec_version::SpecVersion; + use rain_orderbook_app_settings::yaml::FieldErrorKind; + + pub fn get_yaml() -> String { + format!( + r#" +version: {spec_version} +builder: + name: Fixed limit + description: Fixed limit order + short-description: Buy WETH with USDC on Base. + deployments: + some-deployment: + name: Buy WETH with USDC on Base. + description: Buy WETH with USDC for fixed price on Base network. + short-description: Buy WETH with USDC on Base. + deposits: + - token: token1 + min: 0 + presets: + - "0" + - "10" + - "100" + - "1000" + - "10000" + fields: + - binding: binding-1 + name: Field 1 name + description: Field 1 description + presets: + - name: Preset 1 + value: "0x1234567890abcdef1234567890abcdef12345678" + - name: Preset 2 + value: "false" + - name: Preset 3 + value: "some-string" + default: some-default-value + - binding: binding-2 + name: Field 2 name + description: Field 2 description + presets: + - value: "99.2" + - value: "582.1" + - value: "648.239" + show-custom-field: true + other-deployment: + name: Test test + description: Test test test + deposits: + - token: token1 + min: 0 + presets: + - "0" + fields: + - binding: binding-1 + name: Field 1 name + description: Field 1 description + presets: + - name: Preset 1 + value: "0" + - binding: binding-2 + name: Field 2 name + description: Field 2 description + min: 100 + presets: + - value: "0" + select-token-deployment: + name: Select token deployment + description: Select token deployment description + deposits: + - token: token3 + min: 0 + presets: + - "0" + fields: + - binding: binding-1 + name: Field 1 name + description: Field 1 description + presets: + - name: Preset 1 + value: "0" + - binding: binding-2 + name: Field 2 name + description: Field 2 description + min: 100 + presets: + - value: "0" + select-tokens: + - key: token3 + name: Token 3 + description: Token 3 description + - key: token4 + name: Token 4 + description: Token 4 description +networks: + some-network: + rpcs: + - http://localhost:8085/rpc-url + chain-id: 123 + network-id: 123 + currency: ETH + other-network: + rpcs: + - http://localhost:8086/rpc-url + chain-id: 124 + network-id: 124 + currency: ETH2 +subgraphs: + some-sg: https://www.some-sg.com +metaboards: + some-network: https://metaboard.com +rainlangs: + some-deployer: + network: some-network + address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba +orderbooks: + some-orderbook: + address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 + network: some-network + subgraph: some-sg + local-db-remote: remote + deployment-block: 12345 +tokens: + token1: + network: some-network + address: 0xc2132d05d31c914a87c6611c10748aeb04b58e8f + decimals: 6 + label: Token 1 + symbol: T1 + token2: + network: some-network + address: 0x8f3cf7ad23cd3cadbd9735aff958023239c6a063 + decimals: 18 + label: Token 2 + symbol: T2 +scenarios: + some-scenario: + rainlang: some-deployer + bindings: + test-binding: 5 + scenarios: + sub-scenario: + bindings: + another-binding: 300 +orders: + some-order: + inputs: + - token: token1 + vault-id: 1 + outputs: + - token: token2 + vault-id: 1 + rainlang: some-deployer + orderbook: some-orderbook + other-order: + inputs: + - token: token1 + outputs: + - token: token1 + rainlang: some-deployer + orderbook: some-orderbook +deployments: + some-deployment: + scenario: some-scenario + order: some-order + other-deployment: + scenario: some-scenario.sub-scenario + order: other-order + select-token-deployment: + scenario: some-scenario + order: some-order +--- +#test-binding ! +#another-binding ! +#calculate-io +_ _: 0 0; +#handle-io +:; +#handle-add-order +:; +"#, + spec_version = SpecVersion::current() + ) + } + + pub fn get_yaml_with_validation() -> String { + format!( + r#" +version: {spec_version} +builder: + name: Validation Test + description: Test deployment with various validation rules + deployments: + validation-deployment: + name: Validation Test Deployment + description: Testing all validation scenarios + fields: + - binding: price-field + name: Price Field + description: Field with number validation + validation: + type: number + decimals: 18 + minimum: 10 + maximum: 1000 + - binding: quantity-field + name: Quantity Field + description: Field with exclusive bounds + validation: + type: number + decimals: 18 + exclusive-minimum: 0 + exclusive-maximum: 100000 + - binding: percentage-field + name: Percentage Field + description: Field with all number constraints + validation: + type: number + decimals: 18 + minimum: 0 + maximum: 100 + exclusive-maximum: 101 + - binding: simple-number + name: Simple Number + description: Number field with no constraints + validation: + type: number + decimals: 18 + - binding: username-field + name: Username + description: Field with string length validation + validation: + type: string + min-length: 3 + max-length: 20 + - binding: description-field + name: Description + description: Field with only max length + validation: + type: string + max-length: 500 + - binding: code-field + name: Code + description: Field with only min length + validation: + type: string + min-length: 5 + - binding: any-string + name: Any String + description: String field with no constraints + validation: + type: string + - binding: enabled-field + name: Enabled + description: Boolean field + validation: + type: boolean + - binding: preset-number-field + name: Preset Number + description: Number field with presets and validation + presets: + - name: Low + value: 50 + - name: Medium + value: 100 + - name: High + value: 150 + validation: + type: number + decimals: 18 + minimum: 10 + maximum: 200 + - binding: preset-string-field + name: Preset String + description: String field with presets and validation + presets: + - name: Option A + value: alpha + - name: Option B + value: beta + - name: Option C + value: gamma + validation: + type: string + min-length: 4 + max-length: 10 + - binding: no-validation-field + name: No Validation + description: Field without any validation + deposits: + - token: token1 + validation: + minimum: 100 + - token: token2 + validation: + maximum: 10000 + - token: token3 + validation: + exclusive-minimum: 0 + exclusive-maximum: 50000 + - token: token4 + validation: + minimum: 10 + maximum: 1000 + - token: token6 +networks: + test-network: + rpcs: + - http://localhost:8085 + chain-id: 1 + network-id: 1 + currency: ETH +subgraphs: + test-sg: https://test.subgraph.com +rainlangs: + test-deployer: + network: test-network + address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba +orderbooks: + test-orderbook: + address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 + network: test-network + subgraph: test-sg + local-db-remote: remote + deployment-block: 12345 +tokens: + token1: + network: test-network + address: 0xc2132d05d31c914a87c6611c10748aeb04b58e8f + decimals: 6 + label: Token 1 + symbol: T1 + token2: + network: test-network + address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + decimals: 6 + label: Token 2 + symbol: T2 + token3: + network: test-network + address: 0xdAC17F958D2ee523a2206206994597C13D831ec7 + decimals: 6 + label: Token 3 + symbol: T3 + token4: + network: test-network + address: 0x6B175474E89094C44Da98b954EedeAC495271d0F + decimals: 18 + label: Token 4 + symbol: T4 + token5: + network: test-network + address: 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984 + decimals: 18 + label: Token 5 + symbol: T5 + token6: + network: test-network + address: 0x2b591e99afE9f32eAA6214f7B7629768c40Eeb39 + decimals: 8 + label: Token 6 + symbol: T6 +scenarios: + test-scenario: + rainlang: test-deployer + bindings: + test: 1 +orders: + test-order: + inputs: + - token: token1 + vault-id: 1 + outputs: + - token: token1 + vault-id: 1 + rainlang: test-deployer + orderbook: test-orderbook +deployments: + validation-deployment: + scenario: test-scenario + order: test-order +--- +#test ! +#calculate-io +_ _: 0 0; +#handle-io +:; +#handle-add-order +:; +"#, + spec_version = SpecVersion::current() + ) + } + + pub async fn initialize_builder(deployment_name: Option) -> RaindexOrderBuilder { + RaindexOrderBuilder::new_with_deployment( + get_yaml(), + None, + deployment_name.unwrap_or("some-deployment".to_string()), + ) + .await + .unwrap() + } + + pub async fn initialize_builder_with_select_tokens() -> RaindexOrderBuilder { + RaindexOrderBuilder::new_with_deployment( + get_yaml(), + None, + "select-token-deployment".to_string(), + ) + .await + .unwrap() + } + + pub async fn initialize_validation_builder() -> RaindexOrderBuilder { + RaindexOrderBuilder::new_with_deployment( + get_yaml_with_validation(), + None, + "validation-deployment".to_string(), + ) + .await + .unwrap() + } + + #[tokio::test] + async fn test_get_deployment_keys() { + let deployment_keys = RaindexOrderBuilder::get_deployment_keys(get_yaml(), None) + .await + .unwrap(); + assert_eq!( + deployment_keys, + vec![ + "other-deployment", + "select-token-deployment", + "some-deployment" + ] + ); + } + + #[tokio::test] + async fn test_new_with_deployment() { + let res = RaindexOrderBuilder::new_with_deployment( + get_yaml(), + None, + "some-deployment".to_string(), + ) + .await; + assert!(res.is_ok()); + + let err = RaindexOrderBuilder::new_with_deployment( + get_yaml(), + None, + "invalid-deployment".to_string(), + ) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DeploymentNotFound("invalid-deployment".to_string()) + .to_string() + ); + } + + #[tokio::test] + async fn test_get_builder_config() { + let builder = initialize_builder(None).await; + let builder_config = builder.get_builder_config().unwrap(); + assert_eq!(builder_config.name, "Fixed limit".to_string()); + assert_eq!(builder_config.description, "Fixed limit order".to_string()); + assert_eq!(builder_config.deployments.len(), 1); + } + + #[tokio::test] + async fn test_get_current_deployment() { + let builder = initialize_builder(None).await; + let deployment = builder.get_current_deployment().unwrap(); + assert_eq!(deployment.name, "Buy WETH with USDC on Base.".to_string()); + } + + #[tokio::test] + async fn test_get_token_info_local() { + let builder = initialize_builder(None).await; + let token1_info = builder.get_token_info("token1".to_string()).await.unwrap(); + assert_eq!( + token1_info.address.to_string(), + "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" + ); + assert_eq!(token1_info.decimals, 6); + assert_eq!(token1_info.name, "Token 1"); + assert_eq!(token1_info.symbol, "T1"); + } + + #[tokio::test] + async fn test_get_order_details() { + let order_details = RaindexOrderBuilder::get_order_details(get_yaml(), None).unwrap(); + assert_eq!(order_details.name, "Fixed limit"); + assert_eq!(order_details.description, "Fixed limit order"); + assert_eq!( + order_details.short_description, + Some("Buy WETH with USDC on Base.".to_string()) + ); + } + + #[tokio::test] + async fn test_get_deployment_details() { + let deployment_details = + RaindexOrderBuilder::get_deployment_details(get_yaml(), None).unwrap(); + assert_eq!(deployment_details.len(), 3); + let detail = deployment_details.get("some-deployment").unwrap(); + assert_eq!(detail.name, "Buy WETH with USDC on Base."); + } + + #[tokio::test] + async fn test_generate_dotrain_text() { + let builder = initialize_builder(None).await; + let original = builder.get_current_deployment_details().unwrap(); + let dotrain_text = builder.generate_dotrain_text().unwrap(); + let builder2 = RaindexOrderBuilder::new_with_deployment( + dotrain_text, + None, + "some-deployment".to_string(), + ) + .await + .unwrap(); + let restored = builder2.get_current_deployment_details().unwrap(); + assert_eq!(restored, original); + } + + #[tokio::test] + async fn test_get_composed_rainlang() { + let mut builder = initialize_builder(None).await; + let rainlang = builder.get_composed_rainlang().await.unwrap(); + let expected = "/* 0. calculate-io */ \n_ _: 0 0;\n\n/* 1. handle-io */ \n:;".to_string(); + assert_eq!(rainlang, expected); + } + + #[cfg(not(target_family = "wasm"))] + mod select_token_tests { + use super::*; + use httpmock::MockServer; + use serde_json::json; + + pub const SELECT_TOKEN_YAML: &str = r#" +builder: + name: Fixed limit + description: Fixed limit order order + short-description: Buy WETH with USDC on Base. + deployments: + some-deployment: + name: Select token deployment + description: Select token deployment description + deposits: + - token: token3 + min: 0 + presets: + - "0" + fields: + - binding: binding-1 + name: Field 1 name + description: Field 1 description + presets: + - name: Preset 1 + value: "0" + - binding: binding-2 + name: Field 2 name + description: Field 2 description + min: 100 + presets: + - value: "0" + select-tokens: + - key: token3 + name: Token 3 + description: Token 3 description +subgraphs: + some-sg: https://www.some-sg.com +metaboards: + some-network: https://metaboard.com +rainlangs: + some-deployer: + network: some-network + address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba +orderbooks: + some-orderbook: + address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 + network: some-network + subgraph: some-sg + deployment-block: 12345 +scenarios: + some-scenario: + rainlang: some-deployer + bindings: + test-binding: 5 + scenarios: + sub-scenario: + bindings: + another-binding: 300 +orders: + some-order: + rainlang: some-deployer + inputs: + - token: token3 + outputs: + - token: token3 +deployments: + some-deployment: + scenario: some-scenario + order: some-order + normal-deployment: + scenario: some-scenario + order: some-order +--- +#test-binding ! +#another-binding ! +#calculate-io +_ _: 0 0; +#handle-io +:; +#handle-add-order +:; +"#; + + #[tokio::test] + async fn test_get_token_info_remote() { + let server = MockServer::start_async().await; + let yaml = format!( + r#" +version: {spec_version} +networks: + some-network: + rpcs: + - {rpc_url} + chain-id: 123 + network-id: 123 + currency: ETH +{yaml} +"#, + spec_version = SpecVersion::current(), + yaml = SELECT_TOKEN_YAML, + rpc_url = server.url("/rpc") + ); + + server.mock(|when, then| { + when.method("POST").path("/rpc").body_contains("252dba42"); + then.json_body(json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e2031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000", + })); + }); + + let mut builder = RaindexOrderBuilder::new_with_deployment( + yaml.to_string(), + None, + "some-deployment".to_string(), + ) + .await + .unwrap(); + + let err = builder + .get_token_info("token3".to_string()) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + YamlError::Field { + kind: FieldErrorKind::Missing("tokens".to_string()), + location: "root".to_string(), + } + .to_string() + ); + assert_eq!( + err.to_readable_msg(), + "YAML configuration error: Missing required field 'tokens' in root" + ); + + builder + .set_select_token( + "token3".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + ) + .await + .unwrap(); + + let token_info = builder.get_token_info("token3".to_string()).await.unwrap(); + assert_eq!( + token_info.address.to_string(), + "0x0000000000000000000000000000000000000001" + ); + assert_eq!(token_info.decimals, 6); + assert_eq!(token_info.name, "Token 1"); + assert_eq!(token_info.symbol, "T1"); + + let token_infos = builder.get_all_token_infos().await.unwrap(); + assert_eq!(token_infos.len(), 1); + assert_eq!( + token_infos[0].address.to_string(), + "0x0000000000000000000000000000000000000001" + ); + assert_eq!(token_infos[0].decimals, 6); + assert_eq!(token_infos[0].name, "Token 1"); + assert_eq!(token_infos[0].symbol, "T1"); + } + } +} diff --git a/crates/js_api/src/gui/order_operations.rs b/crates/common/src/raindex_order_builder/order_operations.rs similarity index 50% rename from crates/js_api/src/gui/order_operations.rs rename to crates/common/src/raindex_order_builder/order_operations.rs index 147b327965..7d88753c9d 100644 --- a/crates/js_api/src/gui/order_operations.rs +++ b/crates/common/src/raindex_order_builder/order_operations.rs @@ -1,4 +1,5 @@ use super::*; +use crate::{add_order::AddOrderArgs, deposit::DepositArgs, transaction::TransactionArgs}; use alloy::{ primitives::{Bytes, B256, U256}, sol_types::SolCall, @@ -16,9 +17,6 @@ use rain_orderbook_app_settings::{ use rain_orderbook_bindings::{ IRaindexV6::deposit4Call, OrderBook::multicallCall, IERC20::approveCall, }; -use rain_orderbook_common::{ - add_order::AddOrderArgs, deposit::DepositArgs, erc20::ERC20, transaction::TransactionArgs, -}; use std::{collections::HashMap, str::FromStr, sync::Arc}; use url::Url; @@ -29,95 +27,68 @@ pub enum CalldataFunction { DepositAndAddOrder, } -#[derive(Serialize, Deserialize, Debug, Clone, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct TokenAllowance { - #[tsify(type = "string")] - token: Address, - #[tsify(type = "string")] - allowance: U256, + pub token: Address, + pub allowance: U256, } -#[derive(Serialize, Deserialize, Debug, Clone, Tsify)] -pub struct AllowancesResult(Vec); -impl_wasm_traits!(AllowancesResult); +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct AllowancesResult(pub Vec); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum ApprovalCalldataResult { NoDeposits, Calldatas(Vec), } -impl_wasm_traits!(ApprovalCalldataResult); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub enum DepositCalldataResult { NoDeposits, - Calldatas(#[tsify(type = "Hex[]")] Vec), + Calldatas(Vec), } -impl_wasm_traits!(DepositCalldataResult); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct AddOrderCalldataResult(#[tsify(type = "Hex")] Bytes); -impl_wasm_traits!(AddOrderCalldataResult); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct AddOrderCalldataResult(pub Bytes); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct DepositAndAddOrderCalldataResult(#[tsify(type = "Hex")] Bytes); -impl_wasm_traits!(DepositAndAddOrderCalldataResult); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct DepositAndAddOrderCalldataResult(pub Bytes); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct IOVaultIds( - #[tsify(type = "Map>")] - pub HashMap>>, -); -impl_wasm_traits!(IOVaultIds); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct IOVaultIds(pub HashMap>>); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct WithdrawCalldataResult(#[tsify(type = "Hex[]")] Vec); -impl_wasm_traits!(WithdrawCalldataResult); +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub struct WithdrawCalldataResult(pub Vec); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] pub struct ExtendedApprovalCalldata { - #[tsify(type = "string")] pub token: Address, - #[tsify(type = "string")] pub calldata: Bytes, pub symbol: String, } -impl_wasm_traits!(ExtendedApprovalCalldata); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ExternalCall { - #[tsify(type = "string")] pub to: Address, - #[tsify(type = "string")] pub calldata: Bytes, } -impl_wasm_traits!(ExternalCall); -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] pub struct DeploymentTransactionArgs { - approvals: Vec, - #[tsify(type = "string")] - deployment_calldata: Bytes, - #[tsify(type = "string")] - orderbook_address: Address, - chain_id: u32, - #[tsify(type = "ExternalCall | undefined")] - emit_meta_call: Option, + pub approvals: Vec, + pub deployment_calldata: Bytes, + pub orderbook_address: Address, + pub chain_id: u32, + pub emit_meta_call: Option, } -impl_wasm_traits!(DeploymentTransactionArgs); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -#[cfg_attr(target_family = "wasm", derive(Tsify))] pub struct ApprovalCalldata { - #[cfg_attr(target_family = "wasm", tsify(type = "string"))] pub token: Address, - #[cfg_attr(target_family = "wasm", tsify(type = "Hex"))] pub calldata: Bytes, } -#[cfg(target_family = "wasm")] -impl_wasm_traits!(ApprovalCalldata); #[derive(Debug)] pub struct VaultAndDeposit { @@ -126,9 +97,8 @@ pub struct VaultAndDeposit { pub index: usize, } -#[wasm_export] -impl DotrainOrderGui { - fn get_orderbook(&self) -> Result, GuiError> { +impl RaindexOrderBuilder { + fn get_orderbook(&self) -> Result, RaindexOrderBuilderError> { let deployment = self.get_current_deployment()?; deployment .deployment @@ -137,11 +107,11 @@ impl DotrainOrderGui { .as_ref() .orderbook .as_ref() - .ok_or(GuiError::OrderbookNotFound) + .ok_or(RaindexOrderBuilderError::OrderbookNotFound) .cloned() } - fn get_transaction_args(&self) -> Result { + fn get_transaction_args(&self) -> Result { let orderbook = self.get_orderbook()?; Ok(TransactionArgs { orderbook_address: orderbook.address, @@ -156,7 +126,9 @@ impl DotrainOrderGui { }) } - async fn get_deposits_as_map(&self) -> Result, GuiError> { + async fn get_deposits_as_map( + &self, + ) -> Result, RaindexOrderBuilderError> { let mut map: HashMap = HashMap::new(); for d in self.get_deposits()? { let token_info = self.get_token_info(d.token.clone()).await?; @@ -168,8 +140,8 @@ impl DotrainOrderGui { async fn get_vaults_and_deposits( &self, - deployment: &GuiDeploymentCfg, - ) -> Result, GuiError> { + deployment: &OrderBuilderDeploymentCfg, + ) -> Result, RaindexOrderBuilderError> { let deposits_map = self.get_deposits_as_map().await?; let results = deployment .deployment @@ -189,7 +161,7 @@ impl DotrainOrderGui { }) }) }) - .collect::, GuiError>>()?; + .collect::, RaindexOrderBuilderError>>()?; Ok(results) } @@ -197,7 +169,7 @@ impl DotrainOrderGui { &self, deposit_args: &DepositArgs, owner: &str, - ) -> Result { + ) -> Result { let allowance = deposit_args .read_allowance(Address::from_str(owner)?, self.get_transaction_args()?) .await?; @@ -207,10 +179,10 @@ impl DotrainOrderGui { }) } - fn prepare_calldata_generation( + pub fn prepare_calldata_generation( &mut self, calldata_function: CalldataFunction, - ) -> Result { + ) -> Result { let deployment = self.get_current_deployment()?; self.check_select_tokens()?; match calldata_function { @@ -227,37 +199,10 @@ impl DotrainOrderGui { self.get_current_deployment() } - /// Checks token allowances for all deposits against the orderbook contract. - /// - /// Queries the blockchain to determine current allowances for each output token that - /// will be deposited. This helps determine which tokens need approval before - /// the order can be created. - /// - /// ## Examples - /// - /// ```javascript - /// const result = await gui.checkAllowances(walletAddress); - /// if (result.error) { - /// console.error("Allowance check failed:", result.error.readableMsg); - /// return; - /// } - /// const [allowance1, allowance2, ...] = result.value; - /// const { - /// // token is the token address - /// token, - /// // allowance is the current allowance for the token - /// allowance, - /// } = allowance1; - /// ``` - #[wasm_export( - js_name = "checkAllowances", - unchecked_return_type = "AllowancesResult", - return_description = "Current allowances for all deposit tokens" - )] pub async fn check_allowances( &mut self, - #[wasm_export(param_description = "Wallet address to check allowances for")] owner: String, - ) -> Result { + owner: String, + ) -> Result { let deployment = self.prepare_calldata_generation(CalldataFunction::Allowance)?; let vaults_and_deposits = self.get_vaults_and_deposits(&deployment).await?; @@ -281,7 +226,7 @@ impl DotrainOrderGui { let token = order_io .token .as_ref() - .ok_or(GuiError::SelectTokensNotSet)? + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)? .address; let erc20 = ERC20::new(rpcs, token); @@ -293,39 +238,10 @@ impl DotrainOrderGui { Ok(AllowancesResult(results)) } - /// Generates approval calldatas for tokens that need increased allowances. - /// - /// Automatically checks current allowances and generates approval calldata - /// whenever the on-chain allowance differs from the planned deposit amount. - /// - /// ## Examples - /// - /// ```javascript - /// const result = await gui.generateApprovalCalldatas(walletAddress); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// // If there are approvals - /// const [approval1, approval2, ...] = result.value; - /// const { - /// // token is the token address - /// token, - /// // calldata is the approval calldata - /// calldata, - /// } = approval1; - /// ``` - #[wasm_export( - js_name = "generateApprovalCalldatas", - unchecked_return_type = "ApprovalCalldataResult", - return_description = "Approval calldatas needed for insufficient allowances" - )] pub async fn generate_approval_calldatas( &mut self, - #[wasm_export(param_description = "Wallet address that will approve the tokens")] owner: String, - ) -> Result { + ) -> Result { let deposits_map = self.get_deposits_as_map().await?; if deposits_map.is_empty() { return Ok(ApprovalCalldataResult::NoDeposits); @@ -371,7 +287,10 @@ impl DotrainOrderGui { Ok(ApprovalCalldataResult::Calldatas(calldatas)) } - fn populate_vault_ids(&mut self, deployment: &GuiDeploymentCfg) -> Result<(), GuiError> { + fn populate_vault_ids( + &mut self, + deployment: &OrderBuilderDeploymentCfg, + ) -> Result<(), RaindexOrderBuilderError> { self.dotrain_order .dotrain_yaml() .get_order(&deployment.deployment.order.key)? @@ -379,7 +298,10 @@ impl DotrainOrderGui { Ok(()) } - fn update_bindings(&mut self, deployment: &GuiDeploymentCfg) -> Result<(), GuiError> { + fn update_bindings( + &mut self, + deployment: &OrderBuilderDeploymentCfg, + ) -> Result<(), RaindexOrderBuilderError> { self.dotrain_order .dotrain_yaml() .get_scenario(&deployment.deployment.scenario.key)? @@ -387,18 +309,19 @@ impl DotrainOrderGui { self.field_values .keys() .map(|k| Ok((k.clone(), self.get_field_value(k.clone())?.value.clone()))) - .collect::, GuiError>>()?, + .collect::, RaindexOrderBuilderError>>()?, )?; Ok(()) } async fn prepare_add_order_args( &mut self, - deployment: &GuiDeploymentCfg, - ) -> Result { - let dotrain_gui_state_instance_v1 = self.generate_dotrain_gui_state_instance_v1()?; - let dotrain_gui_state_meta = - RainMetaDocumentV1Item::try_from(dotrain_gui_state_instance_v1)?; + deployment: &OrderBuilderDeploymentCfg, + ) -> Result { + let dotrain_builder_state_instance_v1 = + self.generate_dotrain_builder_state_instance_v1()?; + let dotrain_builder_state_meta = + RainMetaDocumentV1Item::try_from(dotrain_builder_state_instance_v1)?; let dotrain_for_deployment = self .dotrain_order @@ -407,40 +330,16 @@ impl DotrainOrderGui { let add_order_args = AddOrderArgs::new_from_deployment( dotrain_for_deployment, deployment.deployment.as_ref().clone(), - Some(vec![dotrain_gui_state_meta]), + Some(vec![dotrain_builder_state_meta]), ) .await?; Ok(add_order_args) } - /// Generates calldata for depositing tokens into orderbook vaults. - /// - /// Creates deposit calldatas for all configured deposits, automatically - /// skipping zero amounts and ensuring vault IDs are properly assigned. - /// - /// ## Examples - /// - /// ```javascript - /// const result = await gui.generateDepositCalldatas(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// // If there are deposits - /// const [depositCalldata1, depositCalldata2, ...] = result.value; - /// const { - /// // calldata is the deposit calldata - /// calldata, - /// } = depositCalldata1; - /// ``` - #[wasm_export( - js_name = "generateDepositCalldatas", - unchecked_return_type = "DepositCalldataResult", - return_description = "Deposit calldatas to execute or NoDeposits if none configured" - )] - pub async fn generate_deposit_calldatas(&mut self) -> Result { + pub async fn generate_deposit_calldatas( + &mut self, + ) -> Result { let deployment = self.prepare_calldata_generation(CalldataFunction::Deposit)?; let vaults_and_deposits = self.get_vaults_and_deposits(&deployment).await?; @@ -462,10 +361,10 @@ impl DotrainOrderGui { let token = order_io .token .as_ref() - .ok_or(GuiError::SelectTokensNotSet)?; + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)?; let vault_id = order_io .vault_id - .ok_or(GuiError::VaultIdNotFound(index.to_string()))?; + .ok_or(RaindexOrderBuilderError::VaultIdNotFound(index.to_string()))?; let decimals = if let Some(decimals) = token.decimals { decimals @@ -487,7 +386,7 @@ impl DotrainOrderGui { decimals, }; let calldata = deposit4Call::try_from(deposit_args) - .map_err(rain_orderbook_common::deposit::DepositError::from)? + .map_err(crate::deposit::DepositError::from)? .abi_encode(); calldatas.push(Bytes::copy_from_slice(&calldata)); } @@ -495,31 +394,9 @@ impl DotrainOrderGui { Ok(DepositCalldataResult::Calldatas(calldatas)) } - /// Generates calldata for adding the order to the orderbook. - /// - /// Creates the addOrder calldata with all field values applied to the - /// Rainlang code and proper vault configurations. - /// - /// ## Examples - /// - /// ```javascript - /// const result = await gui.generateAddOrderCalldata(); - /// if (result.error) { - /// console.error("Cannot create order:", result.error.readableMsg); - /// // Show user what needs to be fixed - /// return; - /// } - /// const addOrderCalldata = result.value; - /// // Do something with the add order calldata - /// ``` - #[wasm_export( - js_name = "generateAddOrderCalldata", - unchecked_return_type = "AddOrderCalldataResult", - return_description = "Encoded addOrder call ready for execution" - )] pub async fn generate_add_order_calldata( &mut self, - ) -> Result { + ) -> Result { let deployment = self.prepare_calldata_generation(CalldataFunction::AddOrder)?; let add_order_args = self.prepare_add_order_args(&deployment).await?; @@ -534,36 +411,9 @@ impl DotrainOrderGui { ))) } - /// Generates a multicall combining all deposits and add order in one calldata. - /// - /// This is the most efficient way to deploy an order, combining all necessary - /// operations into a single calldata to minimize gas costs and ensure atomicity. - /// - /// # Transaction Structure - /// - /// The multicall includes: - /// 1. AddOrder call (always first) - /// 2. All deposit calls for non-zero amounts - /// - /// ## Examples - /// - /// ```javascript - /// const result = await gui.generateDepositAndAddOrderCalldatas(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// const multicallData = result.value; - /// // Do something with the multicall data - /// ``` - #[wasm_export( - js_name = "generateDepositAndAddOrderCalldatas", - unchecked_return_type = "DepositAndAddOrderCalldataResult", - return_description = "Multicall calldata combining deposits and addOrder" - )] pub async fn generate_deposit_and_add_order_calldatas( &mut self, - ) -> Result { + ) -> Result { let deployment = self.prepare_calldata_generation(CalldataFunction::DepositAndAddOrder)?; let mut calls = Vec::new(); @@ -594,74 +444,22 @@ impl DotrainOrderGui { ))) } - /// Configures vault IDs for order inputs or outputs. - /// - /// Sets the vault ID for a specific input or output token. Vault IDs determine - /// which vaults are used for the input or output tokens in the order. - /// - /// ## Examples - /// - /// ```javascript - /// const result1 = gui.setVaultId("input", "token1", "42"); - /// if (result1.error) { - /// console.error("Error:", result1.error.readableMsg); - /// return; - /// } - /// const result2 = gui.setVaultId("output", "token2", "43"); - /// const result3 = gui.setVaultId("output", "token2", undefined); - /// ``` - #[wasm_export(js_name = "setVaultId", unchecked_return_type = "void")] pub fn set_vault_id( &mut self, - #[wasm_export(param_description = "Vault type: 'input' or 'output'")] r#type: VaultType, - #[wasm_export(param_description = "Token key to identify which token to set vault for")] + r#type: VaultType, token: String, - #[wasm_export( - js_name = "vaultId", - param_description = "Vault ID number as string. Omit to clear vault ID" - )] vault_id: Option, - ) -> Result<(), GuiError> { + ) -> Result<(), RaindexOrderBuilderError> { let deployment = self.get_current_deployment()?; self.dotrain_order .dotrain_yaml() .get_order(&deployment.deployment.order.key)? .update_vault_id(r#type, token, vault_id)?; - self.execute_state_update_callback()?; Ok(()) } - /// Gets all configured vault IDs for inputs and outputs. - /// - /// Returns a map with 'input' and 'output' keys, where each value is a map - /// of token keys to their configured vault IDs (or undefined if not set). - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getVaultIds(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// // Access input token vault IDs - /// for (const [tokenKey, vaultId] of result.value.get('input')) { - /// console.log(`Input token ${tokenKey} uses vault ${vaultId || 'none'}`); - /// } - /// - /// // Access output token vault IDs - /// for (const [tokenKey, vaultId] of result.value.get('output')) { - /// console.log(`Output token ${tokenKey} uses vault ${vaultId || 'none'}`); - /// } - /// ``` - #[wasm_export( - js_name = "getVaultIds", - unchecked_return_type = "IOVaultIds", - return_description = "Map with 'input' and 'output' keys containing token-to-vault-ID maps" - )] - pub fn get_vault_ids(&self) -> Result { + pub fn get_vault_ids(&self) -> Result { let deployment = self.get_current_deployment()?; let mut input_map = HashMap::new(); @@ -670,7 +468,7 @@ impl DotrainOrderGui { .token .as_ref() .map(|t| t.key.clone()) - .ok_or(GuiError::SelectTokensNotSet)?; + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)?; input_map.insert(token_key, input.vault_id); } @@ -680,7 +478,7 @@ impl DotrainOrderGui { .token .as_ref() .map(|t| t.key.clone()) - .ok_or(GuiError::SelectTokensNotSet)?; + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)?; output_map.insert(token_key, output.vault_id); } @@ -691,29 +489,7 @@ impl DotrainOrderGui { Ok(IOVaultIds(map)) } - /// Checks if any vault IDs have been configured. - /// - /// Quick validation to determine if vault configuration has started. - /// Useful for UI state management and validation flows. - /// - /// # Examples - /// - /// ```javascript - /// const result = gui.hasAnyVaultId(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// const hasVaults = result.value; - /// // Do something with the has vaults - /// ``` - #[wasm_export( - js_name = "hasAnyVaultId", - unchecked_return_type = "boolean", - return_description = "True if at least one vault ID is set" - )] - pub fn has_any_vault_id(&self) -> Result { + pub fn has_any_vault_id(&self) -> Result { let map = self.get_vault_ids()?; Ok(map .0 @@ -721,57 +497,16 @@ impl DotrainOrderGui { .any(|token_map| token_map.values().any(|vault_id| vault_id.is_some()))) } - #[wasm_export(skip)] - pub fn update_scenario_bindings(&mut self) -> Result<(), GuiError> { + pub fn update_scenario_bindings(&mut self) -> Result<(), RaindexOrderBuilderError> { let deployment = self.get_current_deployment()?; self.update_bindings(&deployment)?; Ok(()) } - /// Gets transaction data for order deployment including approvals. - /// - /// This is the comprehensive function that provides everything needed to deploy - /// an order: approval calldatas, the main deployment transaction, and metadata. - /// Use this for full transaction orchestration. - /// - /// # Transaction Package - /// - /// - `approvals` - Token approval calldatas with symbols for UI - /// - `deploymentCalldata` - Main order deployment calldata - /// - `orderbookAddress` - Target contract address - /// - `chainId` - Network identifier - /// - /// # Examples - /// - /// ```javascript - /// const result = await gui.getDeploymentTransactionArgs(walletAddress); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// const { - /// // approvals is an array of extended approval calldatas - /// // extended approval calldata includes the token address, calldata, and symbol - /// approvals, - /// // deploymentCalldata is the multicall calldata for the order - /// deploymentCalldata, - /// // orderbookAddress is the address of the orderbook - /// orderbookAddress, - /// // chainId is the chain ID of the network - /// chainId, - /// } = result.value; - /// ``` - #[wasm_export( - js_name = "getDeploymentTransactionArgs", - unchecked_return_type = "DeploymentTransactionArgs", - return_description = "Complete transaction package including approvals and deployment calldata" - )] pub async fn get_deployment_transaction_args( &mut self, - #[wasm_export(param_description = "Wallet address that will deploy the order")] owner: String, - ) -> Result { + ) -> Result { let deployment = self.prepare_calldata_generation(CalldataFunction::DepositAndAddOrder)?; let mut approvals = Vec::new(); @@ -780,7 +515,7 @@ impl DotrainOrderGui { let mut output_token_infos = HashMap::new(); for output in deployment.deployment.order.outputs.clone() { if output.token.is_none() { - return Err(GuiError::SelectTokensNotSet); + return Err(RaindexOrderBuilderError::SelectTokensNotSet); } let token = output.token.as_ref().unwrap(); let token_info = self.get_token_info(token.key.clone()).await?; @@ -788,9 +523,9 @@ impl DotrainOrderGui { } for calldata in calldatas.iter() { - let token_info = output_token_infos - .get(&calldata.token) - .ok_or(GuiError::TokenNotFound(calldata.token.to_string()))?; + let token_info = output_token_infos.get(&calldata.token).ok_or( + RaindexOrderBuilderError::TokenNotFound(calldata.token.to_string()), + )?; approvals.push(ExtendedApprovalCalldata { token: calldata.token, calldata: calldata.calldata.clone(), @@ -826,7 +561,7 @@ impl DotrainOrderGui { let addresses = client.get_metaboard_addresses(None, None).await?; let metaboard_address = addresses .first() - .ok_or_else(|| GuiError::NoAddressInMetaboardSubgraph)?; + .ok_or_else(|| RaindexOrderBuilderError::NoAddressInMetaboardSubgraph)?; let calldata = add_order_args.try_into_emit_meta_call()?; calldata.map(|calldata| ExternalCall { @@ -845,14 +580,14 @@ impl DotrainOrderGui { .order .orderbook .as_ref() - .ok_or(GuiError::OrderbookNotFound)? + .ok_or(RaindexOrderBuilderError::OrderbookNotFound)? .address, chain_id: deployment.deployment.order.network.chain_id, emit_meta_call, }) } - fn get_metaboard_client(&self) -> Result { + fn get_metaboard_client(&self) -> Result { let deployment = self.get_current_deployment()?; let orderbook_yaml = self.dotrain_order.orderbook_yaml(); let metaboard_cfg = @@ -860,9 +595,9 @@ impl DotrainOrderGui { Ok(MetaboardSubgraphClient::new(metaboard_cfg.url.clone())) } - async fn should_emit_meta_call(&self) -> Result { - let dotrain_gui_state = self.generate_dotrain_gui_state_instance_v1()?; - let subject = dotrain_gui_state.dotrain_hash(); + async fn should_emit_meta_call(&self) -> Result { + let dotrain_builder_state = self.generate_dotrain_builder_state_instance_v1()?; + let subject = dotrain_builder_state.dotrain_hash(); let client = self.get_metaboard_client()?; match client @@ -871,7 +606,7 @@ impl DotrainOrderGui { { Ok(metas) => Ok(metas.is_empty()), Err(MetaboardSubgraphClientError::Empty(_)) => Ok(true), - Err(err) => Err(GuiError::MetaboardSubgraphClientError(err)), + Err(err) => Err(RaindexOrderBuilderError::MetaboardSubgraphClientError(err)), } } } @@ -879,19 +614,16 @@ impl DotrainOrderGui { #[cfg(test)] mod tests { use super::*; - use crate::gui::tests::{initialize_gui, initialize_gui_with_select_tokens}; - #[cfg(all(test, not(target_family = "wasm")))] - use httpmock::{Method::POST, MockServer}; + use crate::raindex_order_builder::tests::{ + initialize_builder, initialize_builder_with_select_tokens, + }; use rain_metadata::{types::dotrain::source_v1::DotrainSourceV1, RainMetaDocumentV1Item}; - #[cfg(all(test, not(target_family = "wasm")))] - use serde_json::json; - use wasm_bindgen_test::wasm_bindgen_test; - #[wasm_bindgen_test] + #[tokio::test] async fn test_generate_deposit_calldatas() { - let mut gui = initialize_gui(Some("other-deployment".to_string())).await; + let mut builder = initialize_builder(Some("other-deployment".to_string())).await; - let res = gui.generate_deposit_calldatas().await.unwrap(); + let res = builder.generate_deposit_calldatas().await.unwrap(); match res { DepositCalldataResult::Calldatas(_) => { panic!("should not be calldatas"); @@ -899,11 +631,12 @@ mod tests { DepositCalldataResult::NoDeposits => {} } - gui.set_deposit("token1".to_string(), "1200".to_string()) + builder + .set_deposit("token1".to_string(), "1200".to_string()) .await .unwrap(); - let res = gui.generate_deposit_calldatas().await.unwrap(); + let res = builder.generate_deposit_calldatas().await.unwrap(); match res { DepositCalldataResult::Calldatas(calldatas) => { assert_eq!(calldatas.len(), 1); @@ -914,11 +647,12 @@ mod tests { } } - gui.set_deposit("token1".to_string(), "0".to_string()) + builder + .set_deposit("token1".to_string(), "0".to_string()) .await .unwrap(); - let res = gui.generate_deposit_calldatas().await.unwrap(); + let res = builder.generate_deposit_calldatas().await.unwrap(); match res { DepositCalldataResult::Calldatas(calldatas) => { assert!(calldatas.is_empty()); @@ -929,50 +663,50 @@ mod tests { } } - #[wasm_bindgen_test] + #[tokio::test] async fn test_missing_select_tokens() { - let mut gui = initialize_gui_with_select_tokens().await; + let mut builder = initialize_builder_with_select_tokens().await; - let err = gui + let err = builder .check_allowances(Address::random().to_string()) .await .unwrap_err(); assert_eq!( err.to_string(), - GuiError::TokenMustBeSelected("token3".to_string()).to_string() + RaindexOrderBuilderError::TokenMustBeSelected("token3".to_string()).to_string() ); assert_eq!( err.to_readable_msg(), "The token 'token3' must be selected to proceed." ); - let err = gui.generate_deposit_calldatas().await.unwrap_err(); + let err = builder.generate_deposit_calldatas().await.unwrap_err(); assert_eq!( err.to_string(), - GuiError::TokenMustBeSelected("token3".to_string()).to_string() + RaindexOrderBuilderError::TokenMustBeSelected("token3".to_string()).to_string() ); assert_eq!( err.to_readable_msg(), "The token 'token3' must be selected to proceed." ); - let err = gui.generate_add_order_calldata().await.unwrap_err(); + let err = builder.generate_add_order_calldata().await.unwrap_err(); assert_eq!( err.to_string(), - GuiError::TokenMustBeSelected("token3".to_string()).to_string() + RaindexOrderBuilderError::TokenMustBeSelected("token3".to_string()).to_string() ); assert_eq!( err.to_readable_msg(), "The token 'token3' must be selected to proceed." ); - let err = gui + let err = builder .generate_deposit_and_add_order_calldatas() .await .unwrap_err(); assert_eq!( err.to_string(), - GuiError::TokenMustBeSelected("token3".to_string()).to_string() + RaindexOrderBuilderError::TokenMustBeSelected("token3".to_string()).to_string() ); assert_eq!( err.to_readable_msg(), @@ -980,27 +714,27 @@ mod tests { ); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_missing_field_values() { - let mut gui = initialize_gui(None).await; + let mut builder = initialize_builder(None).await; - let err = gui.generate_add_order_calldata().await.unwrap_err(); + let err = builder.generate_add_order_calldata().await.unwrap_err(); assert_eq!( err.to_string(), - GuiError::FieldValueNotSet("Field 2 name".to_string()).to_string() + RaindexOrderBuilderError::FieldValueNotSet("Field 2 name".to_string()).to_string() ); assert_eq!( err.to_readable_msg(), "The value for field 'Field 2 name' is required but has not been set." ); - let err = gui + let err = builder .generate_deposit_and_add_order_calldatas() .await .unwrap_err(); assert_eq!( err.to_string(), - GuiError::FieldValueNotSet("Field 2 name".to_string()).to_string() + RaindexOrderBuilderError::FieldValueNotSet("Field 2 name".to_string()).to_string() ); assert_eq!( err.to_readable_msg(), @@ -1008,26 +742,27 @@ mod tests { ); } - #[wasm_bindgen_test] - async fn test_prepare_add_order_args_injects_gui_meta() { - let mut gui = initialize_gui(None).await; - gui.set_field_value("binding-1".to_string(), "10".to_string()) + #[tokio::test] + async fn test_prepare_add_order_args_injects_builder_meta() { + let mut builder = initialize_builder(None).await; + builder + .set_field_value("binding-1".to_string(), "10".to_string()) .unwrap(); - gui.set_field_value("binding-2".to_string(), "0".to_string()) + builder + .set_field_value("binding-2".to_string(), "0".to_string()) .unwrap(); - let deployment = gui.get_current_deployment().unwrap(); - let trimmed_dotrain = gui + let deployment = builder.get_current_deployment().unwrap(); + let trimmed_dotrain = builder .dotrain_order .generate_dotrain_for_deployment(&deployment.deployment.key) .unwrap(); - let add_order_args = gui + let add_order_args = builder .prepare_add_order_args(&deployment) .await .expect("add order args"); - // additional_meta should carry the GUI state document let additional_meta = add_order_args .additional_meta .as_ref() @@ -1035,10 +770,9 @@ mod tests { assert_eq!(additional_meta.len(), 1); assert_eq!( additional_meta[0].magic, - rain_metadata::KnownMagic::DotrainGuiStateV1 + rain_metadata::KnownMagic::OrderBuilderStateV1 ); - // try_into_emit_meta_call should emit a DotrainSourceV1 for the trimmed deployment dotrain let emit_meta_call = add_order_args .try_into_emit_meta_call() .expect("emit meta call err") @@ -1048,149 +782,67 @@ mod tests { let dotrain_source = DotrainSourceV1::try_from(decoded[0].clone()).unwrap(); assert_eq!(dotrain_source.0, trimmed_dotrain); - // Subject must match the dotrain hash assert_eq!( emit_meta_call.subject, DotrainSourceV1(trimmed_dotrain).hash() ); } - #[cfg(all(test, not(target_family = "wasm")))] - async fn initialize_gui_with_metaboard_url(url: &str) -> DotrainOrderGui { - let yaml = crate::gui::tests::get_yaml().replace("https://metaboard.com", url); - DotrainOrderGui::new_with_deployment(yaml, None, "some-deployment".to_string(), None) - .await - .unwrap() - } - - #[cfg(all(test, not(target_family = "wasm")))] - #[tokio::test] - async fn test_should_emit_meta_call_false_when_meta_exists() { - let server = MockServer::start_async().await; - server.mock(|when, then| { - when.method(POST).path("/"); - then.status(200).json_body_obj(&json!({ - "data": { - "metaV1S": [ - { - "meta": "0x01", - "metaHash": "0x00", - "sender": "0x00", - "id": "0x00", - "metaBoard": { - "id": "0x00", - "metas": [], - "address": "0x00" - }, - "subject": "0x00" - } - ] - } - })); - }); - - let mut gui = initialize_gui_with_metaboard_url(&server.url("/")).await; - gui.set_field_value("binding-1".to_string(), "10".to_string()) - .unwrap(); - gui.set_field_value("binding-2".to_string(), "0".to_string()) - .unwrap(); - - assert!(!gui.should_emit_meta_call().await.unwrap()); - } - - #[cfg(all(test, not(target_family = "wasm")))] #[tokio::test] - async fn test_should_emit_meta_call_true_when_no_meta() { - let server = MockServer::start_async().await; - server.mock(|when, then| { - when.method(POST).path("/"); - then.status(200).json_body_obj(&json!({ - "data": { - "metaV1S": [] - } - })); - }); - - let mut gui = initialize_gui_with_metaboard_url(&server.url("/")).await; - gui.set_field_value("binding-1".to_string(), "10".to_string()) - .unwrap(); - gui.set_field_value("binding-2".to_string(), "0".to_string()) - .unwrap(); - - assert!(gui.should_emit_meta_call().await.unwrap()); - } - - #[cfg(all(test, not(target_family = "wasm")))] - #[tokio::test] - async fn test_should_emit_meta_call_propagates_errors() { - let server = MockServer::start_async().await; - server.mock(|when, then| { - when.method(POST).path("/"); - then.status(500); - }); - - let mut gui = initialize_gui_with_metaboard_url(&server.url("/")).await; - gui.set_field_value("binding-1".to_string(), "10".to_string()) - .unwrap(); - gui.set_field_value("binding-2".to_string(), "0".to_string()) - .unwrap(); - - let err = gui.should_emit_meta_call().await.unwrap_err(); - assert!(matches!(err, GuiError::MetaboardSubgraphClientError(_))); - } - - #[wasm_bindgen_test] async fn test_get_vault_ids() { - let gui = initialize_gui(None).await; - let res = gui.get_vault_ids().unwrap(); + let builder = initialize_builder(None).await; + let res = builder.get_vault_ids().unwrap(); assert_eq!(res.0.len(), 2); assert_eq!(res.0["input"]["token1"], Some(U256::from(1))); assert_eq!(res.0["output"]["token2"], Some(U256::from(1))); - let mut gui = initialize_gui(Some("other-deployment".to_string())).await; + let mut builder = initialize_builder(Some("other-deployment".to_string())).await; - let res = gui.get_vault_ids().unwrap(); + let res = builder.get_vault_ids().unwrap(); assert_eq!(res.0.len(), 2); assert_eq!(res.0["input"]["token1"], None); assert_eq!(res.0["output"]["token1"], None); - gui.set_vault_id( - VaultType::Input, - "token1".to_string(), - Some("999".to_string()), - ) - .unwrap(); - gui.set_vault_id( - VaultType::Output, - "token1".to_string(), - Some("888".to_string()), - ) - .unwrap(); + builder + .set_vault_id( + VaultType::Input, + "token1".to_string(), + Some("999".to_string()), + ) + .unwrap(); + builder + .set_vault_id( + VaultType::Output, + "token1".to_string(), + Some("888".to_string()), + ) + .unwrap(); - let res = gui.get_vault_ids().unwrap(); + let res = builder.get_vault_ids().unwrap(); assert_eq!(res.0.len(), 2); assert_eq!(res.0["input"]["token1"], Some(U256::from(999))); assert_eq!(res.0["output"]["token1"], Some(U256::from(888))); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_has_any_vault_id() { - let mut gui = initialize_gui(Some("other-deployment".to_string())).await; - assert!(!gui.has_any_vault_id().unwrap()); - gui.set_vault_id( - VaultType::Input, - "token1".to_string(), - Some("1".to_string()), - ) - .unwrap(); - assert!(gui.has_any_vault_id().unwrap()); + let mut builder = initialize_builder(Some("other-deployment".to_string())).await; + assert!(!builder.has_any_vault_id().unwrap()); + builder + .set_vault_id( + VaultType::Input, + "token1".to_string(), + Some("1".to_string()), + ) + .unwrap(); + assert!(builder.has_any_vault_id().unwrap()); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_update_scenario_bindings() { - let mut gui = initialize_gui(Some("other-deployment".to_string())).await; + let mut builder = initialize_builder(Some("other-deployment".to_string())).await; - let deployment = gui.get_current_deployment().unwrap(); + let deployment = builder.get_current_deployment().unwrap(); assert!(!deployment .deployment .scenario @@ -1202,14 +854,113 @@ mod tests { .bindings .contains_key("binding-2")); - gui.set_field_value("binding-1".to_string(), "100".to_string()) + builder + .set_field_value("binding-1".to_string(), "100".to_string()) .unwrap(); - gui.set_field_value("binding-2".to_string(), "200".to_string()) + builder + .set_field_value("binding-2".to_string(), "200".to_string()) .unwrap(); - gui.update_scenario_bindings().unwrap(); + builder.update_scenario_bindings().unwrap(); - let deployment = gui.get_current_deployment().unwrap(); + let deployment = builder.get_current_deployment().unwrap(); assert_eq!(deployment.deployment.scenario.bindings["binding-1"], "100"); assert_eq!(deployment.deployment.scenario.bindings["binding-2"], "200"); } + + #[cfg(not(target_family = "wasm"))] + mod non_wasm_tests { + use super::*; + use crate::raindex_order_builder::tests::get_yaml; + use httpmock::{Method::POST, MockServer}; + use serde_json::json; + + async fn initialize_builder_with_metaboard_url(url: &str) -> RaindexOrderBuilder { + let yaml = get_yaml().replace("https://metaboard.com", url); + RaindexOrderBuilder::new_with_deployment(yaml, None, "some-deployment".to_string()) + .await + .unwrap() + } + + #[tokio::test] + async fn test_should_emit_meta_call_false_when_meta_exists() { + let server = MockServer::start_async().await; + server.mock(|when, then| { + when.method(POST).path("/"); + then.status(200).json_body_obj(&json!({ + "data": { + "metaV1S": [ + { + "meta": "0x01", + "metaHash": "0x00", + "sender": "0x00", + "id": "0x00", + "metaBoard": { + "id": "0x00", + "metas": [], + "address": "0x00" + }, + "subject": "0x00" + } + ] + } + })); + }); + + let mut builder = initialize_builder_with_metaboard_url(&server.url("/")).await; + builder + .set_field_value("binding-1".to_string(), "10".to_string()) + .unwrap(); + builder + .set_field_value("binding-2".to_string(), "0".to_string()) + .unwrap(); + + assert!(!builder.should_emit_meta_call().await.unwrap()); + } + + #[tokio::test] + async fn test_should_emit_meta_call_true_when_no_meta() { + let server = MockServer::start_async().await; + server.mock(|when, then| { + when.method(POST).path("/"); + then.status(200).json_body_obj(&json!({ + "data": { + "metaV1S": [] + } + })); + }); + + let mut builder = initialize_builder_with_metaboard_url(&server.url("/")).await; + builder + .set_field_value("binding-1".to_string(), "10".to_string()) + .unwrap(); + builder + .set_field_value("binding-2".to_string(), "0".to_string()) + .unwrap(); + + assert!(builder.should_emit_meta_call().await.unwrap()); + } + + #[tokio::test] + async fn test_should_emit_meta_call_propagates_errors() { + let server = MockServer::start_async().await; + server.mock(|when, then| { + when.method(POST).path("/"); + then.status(500); + }); + + let mut builder = initialize_builder_with_metaboard_url(&server.url("/")).await; + builder + .set_field_value("binding-1".to_string(), "10".to_string()) + .unwrap(); + builder + .set_field_value("binding-2".to_string(), "0".to_string()) + .unwrap(); + + let err = builder.should_emit_meta_call().await.unwrap_err(); + assert!(matches!( + err, + RaindexOrderBuilderError::MetaboardSubgraphClientError(_) + )); + } + } } diff --git a/crates/common/src/raindex_order_builder/select_tokens.rs b/crates/common/src/raindex_order_builder/select_tokens.rs new file mode 100644 index 0000000000..117dbb2037 --- /dev/null +++ b/crates/common/src/raindex_order_builder/select_tokens.rs @@ -0,0 +1,851 @@ +use super::*; +use crate::raindex_client::vaults::AccountBalance; +use futures::StreamExt; +use rain_math_float::Float; +use rain_orderbook_app_settings::{ + deployment::DeploymentCfg, network::NetworkCfg, order::OrderCfg, + order_builder::OrderBuilderSelectTokensCfg, token::TokenCfg, yaml::YamlParsableHash, +}; +use std::str::FromStr; + +const MAX_CONCURRENT_FETCHES: usize = 5; + +impl RaindexOrderBuilder { + pub fn get_select_tokens( + &self, + ) -> Result, RaindexOrderBuilderError> { + let select_tokens = OrderBuilderCfg::parse_select_tokens( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )?; + Ok(select_tokens.unwrap_or(vec![])) + } + + pub fn is_select_token_set(&self, key: String) -> Result { + Ok(self.dotrain_order.orderbook_yaml().get_token(&key).is_ok()) + } + + pub fn check_select_tokens(&self) -> Result<(), RaindexOrderBuilderError> { + let select_tokens = OrderBuilderCfg::parse_select_tokens( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )?; + + if let Some(select_tokens) = select_tokens { + for select_token in select_tokens { + if self + .dotrain_order + .orderbook_yaml() + .get_token(&select_token.key) + .is_err() + { + return Err(RaindexOrderBuilderError::TokenMustBeSelected( + select_token.key.clone(), + )); + } + } + } + + Ok(()) + } + + pub async fn set_select_token( + &mut self, + key: String, + address: String, + ) -> Result<(), RaindexOrderBuilderError> { + let select_tokens = OrderBuilderCfg::parse_select_tokens( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )? + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)?; + if !select_tokens.iter().any(|token| token.key == key) { + return Err(RaindexOrderBuilderError::TokenNotFound(key.clone())); + } + + if TokenCfg::parse_from_yaml(self.dotrain_order.dotrain_yaml().documents, &key, None) + .is_ok() + { + TokenCfg::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key)?; + } + + let address = Address::from_str(&address)?; + + let order_key = DeploymentCfg::parse_order_key( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )?; + let network_key = + OrderCfg::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?; + let rpcs = + NetworkCfg::parse_rpcs(self.dotrain_order.dotrain_yaml().documents, &network_key)?; + + let erc20 = ERC20::new(rpcs, address); + let token_info = erc20.token_info(None).await?; + + TokenCfg::add_record_to_yaml( + self.dotrain_order.orderbook_yaml().documents, + &key, + &network_key, + &address.to_string(), + Some(&token_info.decimals.to_string()), + Some(&token_info.name), + Some(&token_info.symbol), + )?; + + Ok(()) + } + + pub fn unset_select_token(&mut self, key: String) -> Result<(), RaindexOrderBuilderError> { + let select_tokens = OrderBuilderCfg::parse_select_tokens( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )? + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)?; + if !select_tokens.iter().any(|token| token.key == key) { + return Err(RaindexOrderBuilderError::TokenNotFound(key.clone())); + } + + TokenCfg::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key)?; + + Ok(()) + } + + pub fn are_all_tokens_selected(&self) -> Result { + let select_tokens = self.get_select_tokens()?; + for token in select_tokens { + if !self.is_select_token_set(token.key)? { + return Ok(false); + } + } + Ok(true) + } + + pub async fn get_all_tokens( + &self, + search: Option, + ) -> Result, RaindexOrderBuilderError> { + let order_key = DeploymentCfg::parse_order_key( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )?; + let network_key = + OrderCfg::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?; + let tokens = self.dotrain_order.orderbook_yaml().get_tokens()?; + + let mut fetch_futures = Vec::new(); + + for (_, token) in tokens + .into_iter() + .filter(|(_, token)| token.network.key == network_key) + { + fetch_futures.push(async move { ExtendedTokenInfo::from_token_cfg(&token).await }); + } + + let mut results: Vec = futures::stream::iter(fetch_futures) + .buffer_unordered(MAX_CONCURRENT_FETCHES) + .filter_map(|res| async { res.ok() }) + .collect() + .await; + results.sort_by(|a, b| { + a.address + .to_string() + .to_lowercase() + .cmp(&b.address.to_string().to_lowercase()) + }); + results.dedup_by(|a, b| { + a.address.to_string().to_lowercase() == b.address.to_string().to_lowercase() + }); + + if let Some(search_term) = search { + if !search_term.is_empty() { + let search_lower = search_term.to_lowercase(); + results.retain(|token| { + token.name.to_lowercase().contains(&search_lower) + || token.symbol.to_lowercase().contains(&search_lower) + || token + .address + .to_string() + .to_lowercase() + .contains(&search_lower) + }); + } + } + + Ok(results) + } + + pub async fn get_account_balance( + &self, + token_address: String, + owner: String, + ) -> Result { + let order_key = DeploymentCfg::parse_order_key( + self.dotrain_order.dotrain_yaml().documents, + &self.selected_deployment, + )?; + let network_key = + OrderCfg::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?; + let network = self + .dotrain_order + .orderbook_yaml() + .get_network(&network_key)?; + + let erc20 = ERC20::new(network.rpcs, Address::from_str(&token_address)?); + let decimals = erc20.decimals().await?; + let balance = erc20 + .get_account_balance(Address::from_str(&owner)?) + .await?; + let float_balance = Float::from_fixed_decimal(balance, decimals)?; + + Ok(AccountBalance::new(float_balance, float_balance.format()?)) + } +} + +#[cfg(test)] +impl RaindexOrderBuilder { + pub fn add_record_to_yaml( + &self, + key: String, + network_key: String, + address: String, + decimals: String, + label: String, + symbol: String, + ) { + TokenCfg::add_record_to_yaml( + self.dotrain_order.orderbook_yaml().documents, + &key, + &network_key, + &address, + Some(&decimals), + Some(&label), + Some(&symbol), + ) + .unwrap(); + } + + pub fn remove_record_from_yaml(&self, key: String) { + TokenCfg::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key) + .unwrap(); + } +} + +#[cfg(test)] +mod tests { + use crate::raindex_order_builder::tests::{ + initialize_builder, initialize_builder_with_select_tokens, + }; + use crate::raindex_order_builder::RaindexOrderBuilderError; + + #[tokio::test] + async fn test_get_select_tokens() { + let builder = initialize_builder_with_select_tokens().await; + let select_tokens = builder.get_select_tokens().unwrap(); + assert_eq!(select_tokens.len(), 2); + assert_eq!(select_tokens[0].key, "token3"); + assert_eq!(select_tokens[1].key, "token4"); + + let builder = initialize_builder(None).await; + let select_tokens = builder.get_select_tokens().unwrap(); + assert_eq!(select_tokens.len(), 0); + } + + #[tokio::test] + async fn test_is_select_token_set() { + let builder = initialize_builder_with_select_tokens().await; + let is_select_token_set = builder.is_select_token_set("token3".to_string()).unwrap(); + assert!(!is_select_token_set); + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + + let is_select_token_set = builder.is_select_token_set("token3".to_string()).unwrap(); + assert!(is_select_token_set); + } + + #[tokio::test] + async fn test_check_select_tokens() { + let builder = initialize_builder_with_select_tokens().await; + + let err = builder.check_select_tokens().unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::TokenMustBeSelected("token3".to_string()).to_string() + ); + assert_eq!( + err.to_readable_msg(), + "The token 'token3' must be selected to proceed." + ); + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + + let err = builder.check_select_tokens().unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::TokenMustBeSelected("token4".to_string()).to_string() + ); + + builder.add_record_to_yaml( + "token4".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "18".to_string(), + "Token 4".to_string(), + "T4".to_string(), + ); + + assert!(builder.check_select_tokens().is_ok()); + } + + #[tokio::test] + async fn test_remove_select_token() { + let mut builder = initialize_builder_with_select_tokens().await; + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + + let err = builder + .unset_select_token("token5".to_string()) + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::TokenNotFound("token5".to_string()).to_string() + ); + assert_eq!( + err.to_readable_msg(), + "The token 'token5' could not be found in the YAML configuration." + ); + + assert!(builder.unset_select_token("token3".to_string()).is_ok()); + assert!(!builder.is_select_token_set("token3".to_string()).unwrap()); + + let mut builder = initialize_builder(None).await; + let err = builder + .unset_select_token("token3".to_string()) + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::SelectTokensNotSet.to_string() + ); + assert_eq!( + err.to_readable_msg(), + "No tokens have been configured for selection. Please check your YAML configuration." + ); + } + + #[tokio::test] + async fn test_are_all_tokens_selected() { + let builder = initialize_builder_with_select_tokens().await; + + let are_all_tokens_selected = builder.are_all_tokens_selected().unwrap(); + assert!(!are_all_tokens_selected); + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + + let are_all_tokens_selected = builder.are_all_tokens_selected().unwrap(); + assert!(!are_all_tokens_selected); + + builder.add_record_to_yaml( + "token4".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "18".to_string(), + "Token 4".to_string(), + "T4".to_string(), + ); + + let are_all_tokens_selected = builder.are_all_tokens_selected().unwrap(); + assert!(are_all_tokens_selected); + } + + #[tokio::test] + async fn test_get_all_tokens() { + let builder = initialize_builder_with_select_tokens().await; + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + builder.add_record_to_yaml( + "token4".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "6".to_string(), + "Token 4".to_string(), + "T4".to_string(), + ); + builder.add_record_to_yaml( + "token-other".to_string(), + "other-network".to_string(), + "0x0000000000000000000000000000000000000003".to_string(), + "8".to_string(), + "Token Other".to_string(), + "TO".to_string(), + ); + + let tokens = builder.get_all_tokens(None).await.unwrap(); + assert_eq!(tokens.len(), 4); + assert_eq!( + tokens[0].address.to_string(), + "0x0000000000000000000000000000000000000001" + ); + assert_eq!(tokens[0].decimals, 18); + assert_eq!(tokens[0].name, "Token 3"); + assert_eq!(tokens[0].symbol, "T3"); + assert_eq!( + tokens[1].address.to_string(), + "0x0000000000000000000000000000000000000002" + ); + assert_eq!(tokens[1].decimals, 6); + assert_eq!(tokens[1].name, "Token 4"); + assert_eq!(tokens[1].symbol, "T4"); + } + + #[tokio::test] + async fn test_get_all_tokens_search_by_name() { + let builder = initialize_builder_with_select_tokens().await; + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + builder.add_record_to_yaml( + "token4".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "6".to_string(), + "Token 4".to_string(), + "T4".to_string(), + ); + + let tokens = builder + .get_all_tokens(Some("Token 3".to_string())) + .await + .unwrap(); + assert_eq!(tokens.len(), 1); + assert_eq!(tokens[0].name, "Token 3"); + } + + #[tokio::test] + async fn test_get_all_tokens_search_empty_string() { + let builder = initialize_builder_with_select_tokens().await; + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + + let tokens = builder.get_all_tokens(Some("".to_string())).await.unwrap(); + assert_eq!(tokens.len(), 3); + } + + #[tokio::test] + async fn test_get_all_tokens_search_by_symbol() { + let builder = initialize_builder_with_select_tokens().await; + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + builder.add_record_to_yaml( + "token4".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "6".to_string(), + "Token 4".to_string(), + "T4".to_string(), + ); + + let tokens = builder + .get_all_tokens(Some("T4".to_string())) + .await + .unwrap(); + assert_eq!(tokens.len(), 1); + assert_eq!(tokens[0].symbol, "T4"); + } + + #[tokio::test] + async fn test_get_all_tokens_search_by_address() { + let builder = initialize_builder_with_select_tokens().await; + + builder.add_record_to_yaml( + "token3".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "18".to_string(), + "Token 3".to_string(), + "T3".to_string(), + ); + builder.add_record_to_yaml( + "token4".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "6".to_string(), + "Token 4".to_string(), + "T4".to_string(), + ); + + let tokens = builder + .get_all_tokens(Some( + "0x0000000000000000000000000000000000000002".to_string(), + )) + .await + .unwrap(); + assert_eq!(tokens.len(), 1); + assert_eq!( + tokens[0].address.to_string(), + "0x0000000000000000000000000000000000000002" + ); + } + + #[tokio::test] + async fn test_get_all_tokens_search_partial_match() { + let builder = initialize_builder_with_select_tokens().await; + + builder.add_record_to_yaml( + "usdc".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + "6".to_string(), + "USD Coin".to_string(), + "USDC".to_string(), + ); + builder.add_record_to_yaml( + "usdt".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + "6".to_string(), + "Tether USD".to_string(), + "USDT".to_string(), + ); + builder.add_record_to_yaml( + "eth".to_string(), + "some-network".to_string(), + "0x0000000000000000000000000000000000000003".to_string(), + "18".to_string(), + "Ethereum".to_string(), + "ETH".to_string(), + ); + + let tokens = builder + .get_all_tokens(Some("USD".to_string())) + .await + .unwrap(); + assert_eq!(tokens.len(), 2); + + for token in &tokens { + assert!( + token.name.contains("USD") || token.symbol.contains("USD"), + "Token {} should contain 'USD' in name or symbol", + token.symbol + ); + } + + let tokens = builder + .get_all_tokens(Some("000000000000000000000000000000000000000".to_string())) + .await + .unwrap(); + assert_eq!(tokens.len(), 3); + } + + #[cfg(not(target_family = "wasm"))] + mod mockserver_tests { + use super::*; + use crate::raindex_order_builder::RaindexOrderBuilder; + use alloy::primitives::Address; + use httpmock::MockServer; + use rain_orderbook_app_settings::spec_version::SpecVersion; + use serde_json::json; + use std::str::FromStr; + + const TEST_YAML_TEMPLATE: &str = r#" +builder: + name: Fixed limit + description: Fixed limit order + short-description: Buy WETH with USDC on Base. + deployments: + some-deployment: + name: Select token deployment + description: Select token deployment description + deposits: + - token: token3 + min: 0 + presets: + - "0" + fields: + - binding: binding-1 + name: Field 1 name + description: Field 1 description + presets: + - name: Preset 1 + value: "0" + - binding: binding-2 + name: Field 2 name + description: Field 2 description + min: 100 + presets: + - value: "0" + select-tokens: + - key: token3 + name: Token 3 + description: Token 3 description + normal-deployment: + name: Normal deployment + description: Normal deployment description + deposits: + - token: token3 + fields: + - binding: binding-1 + name: Field 1 name + default: 10 +subgraphs: + some-sg: https://www.some-sg.com +metaboards: + some-network: https://metaboard.com +rainlangs: + some-deployer: + network: some-network + address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba +orderbooks: + some-orderbook: + address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 + network: some-network + subgraph: some-sg + deployment-block: 12345 +scenarios: + some-scenario: + rainlang: some-deployer + bindings: + test-binding: 5 + scenarios: + sub-scenario: + bindings: + another-binding: 300 +orders: + some-order: + rainlang: some-deployer + inputs: + - token: token3 + outputs: + - token: token3 +deployments: + some-deployment: + scenario: some-scenario + order: some-order + normal-deployment: + scenario: some-scenario + order: some-order +--- +#test-binding ! +#another-binding ! +#calculate-io +_ _: 0 0; +#handle-io +:; +#handle-add-order +:; +"#; + + #[tokio::test] + async fn test_set_select_token() { + let server = MockServer::start_async().await; + let yaml = format!( + r#" +version: {spec_version} +networks: + some-network: + rpcs: + - {rpc_url} + chain-id: 123 + network-id: 123 + currency: ETH +{yaml} +"#, + spec_version = SpecVersion::current(), + yaml = TEST_YAML_TEMPLATE, + rpc_url = server.url("/rpc") + ); + + server.mock(|when, then| { + when.method("POST").path("/rpc").body_contains("252dba42"); + then.json_body(json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e2031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000", + })); + }); + + let mut builder = RaindexOrderBuilder::new_with_deployment( + yaml.to_string(), + None, + "some-deployment".to_string(), + ) + .await + .unwrap(); + + let deployment = builder.get_current_deployment().unwrap(); + assert_eq!(deployment.deployment.order.inputs[0].token, None); + assert_eq!(deployment.deployment.order.outputs[0].token, None); + + builder + .set_select_token( + "token3".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + ) + .await + .unwrap(); + assert!(builder.is_select_token_set("token3".to_string()).unwrap()); + + let deployment = builder.get_current_deployment().unwrap(); + let token = deployment.deployment.order.inputs[0] + .token + .as_ref() + .unwrap(); + assert_eq!( + token.address, + Address::from_str("0x0000000000000000000000000000000000000001").unwrap() + ); + assert_eq!(token.decimals, Some(6)); + assert_eq!(token.label, Some("Token 1".to_string())); + assert_eq!(token.symbol, Some("T1".to_string())); + + let err = builder + .set_select_token( + "token4".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + ) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::TokenNotFound("token4".to_string()).to_string() + ); + assert_eq!( + err.to_readable_msg(), + "The token 'token4' could not be found in the YAML configuration." + ); + + let mut builder = RaindexOrderBuilder::new_with_deployment( + yaml.to_string(), + None, + "normal-deployment".to_string(), + ) + .await + .unwrap(); + + let err = builder + .set_select_token( + "token3".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + ) + .await + .unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::SelectTokensNotSet.to_string() + ); + assert_eq!( + err.to_readable_msg(), + "No tokens have been configured for selection. Please check your YAML configuration." + ); + } + + #[tokio::test] + async fn test_get_account_balance() { + let server = MockServer::start_async().await; + let yaml = format!( + r#" +version: {spec_version} +networks: + some-network: + rpcs: + - {rpc_url} + chain-id: 123 + network-id: 123 + currency: ETH +{yaml} +"#, + spec_version = SpecVersion::current(), + yaml = TEST_YAML_TEMPLATE, + rpc_url = server.url("/rpc") + ); + + server.mock(|when, then| { + when.method("POST").path("/rpc").body_contains("0x313ce567"); + then.json_body(json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0x0000000000000000000000000000000000000000000000000000000000000012", + })); + }); + server.mock(|when, then| { + when.method("POST").path("/rpc").body_contains("0x70a08231"); + then.json_body(json!({ + "jsonrpc": "2.0", + "id": 1, + "result": "0x00000000000000000000000000000000000000000000000000000000000003e8", + })); + }); + + let builder = RaindexOrderBuilder::new_with_deployment( + yaml.to_string(), + None, + "some-deployment".to_string(), + ) + .await + .unwrap(); + + let balance = builder + .get_account_balance( + "0x0000000000000000000000000000000000000001".to_string(), + "0x0000000000000000000000000000000000000002".to_string(), + ) + .await + .unwrap(); + + assert_eq!(balance.formatted_balance(), "1e-15"); + } + } +} diff --git a/crates/js_api/src/gui/state_management.rs b/crates/common/src/raindex_order_builder/state_management.rs similarity index 55% rename from crates/js_api/src/gui/state_management.rs rename to crates/common/src/raindex_order_builder/state_management.rs index 6a57c32100..839026a92d 100644 --- a/crates/js_api/src/gui/state_management.rs +++ b/crates/common/src/raindex_order_builder/state_management.rs @@ -1,11 +1,11 @@ use super::*; use rain_metadata::types::dotrain::{ - gui_state_v1::{DotrainGuiStateV1, ShortenedTokenCfg, ValueCfg}, + order_builder_state_v1::{OrderBuilderStateV1, ShortenedTokenCfg, ValueCfg}, source_v1::DotrainSourceV1, }; use rain_orderbook_app_settings::{ - gui::GuiDepositCfg, order::{OrderIOCfg, VaultType}, + order_builder::OrderBuilderDepositCfg, token::TokenCfg, }; use sha2::{Digest, Sha256}; @@ -14,40 +14,40 @@ use std::{ sync::{Arc, RwLock}, }; use strict_yaml_rust::StrictYaml; -use wasm_bindgen::JsValue; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(rename_all = "camelCase")] -pub struct AllGuiConfig { - pub field_definitions_without_defaults: Vec, - pub field_definitions_with_defaults: Vec, - pub deposits: Vec, +pub struct AllBuilderConfig { + pub field_definitions_without_defaults: Vec, + pub field_definitions_with_defaults: Vec, + pub deposits: Vec, pub order_inputs: Vec, pub order_outputs: Vec, } -impl_wasm_traits!(AllGuiConfig); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -struct SerializedGuiState { - field_values: BTreeMap, - deposits: BTreeMap, +struct SerializedBuilderState { + field_values: BTreeMap, + deposits: BTreeMap, select_tokens: BTreeMap, vault_ids: BTreeMap<(VaultType, String), Option>, dotrain_hash: String, selected_deployment: String, } -#[wasm_export] -impl DotrainOrderGui { - fn create_preset(value: &field_values::PairValue, default_value: String) -> GuiPresetCfg { +impl RaindexOrderBuilder { + fn create_preset( + value: &field_values::PairValue, + default_value: String, + ) -> OrderBuilderPresetCfg { if value.is_preset { - GuiPresetCfg { + OrderBuilderPresetCfg { id: value.value.clone(), name: None, value: default_value, } } else { - GuiPresetCfg { + OrderBuilderPresetCfg { id: "".to_string(), name: None, value: value.value.clone(), @@ -55,7 +55,7 @@ impl DotrainOrderGui { } } - fn preset_to_pair_value(preset: GuiPresetCfg) -> field_values::PairValue { + fn preset_to_pair_value(preset: OrderBuilderPresetCfg) -> field_values::PairValue { if !preset.id.is_empty() { field_values::PairValue { is_preset: true, @@ -73,7 +73,7 @@ impl DotrainOrderGui { documents: Vec>>, order_key: &str, is_input: bool, - ) -> Result>, GuiError> { + ) -> Result>, RaindexOrderBuilderError> { let mut vault_ids = BTreeMap::new(); let r#type = if is_input { VaultType::Input @@ -86,14 +86,14 @@ impl DotrainOrderGui { Ok(vault_ids) } - #[wasm_export(skip)] - pub fn generate_dotrain_gui_state_instance_v1(&self) -> Result { + pub fn generate_dotrain_builder_state_instance_v1( + &self, + ) -> Result { let trimmed_dotrain = self .dotrain_order .generate_dotrain_for_deployment(&self.selected_deployment)?; let dotrain_hash = DotrainSourceV1(trimmed_dotrain.clone()).hash(); - // Use normalized deposit amounts (resolve presets to actual values) let deposits = self .get_deposits()? .into_iter() @@ -109,13 +109,11 @@ impl DotrainOrderGui { }) .collect(); - // Prefer the resolved tokens from the current deployment (captures user selections) let select_tokens = { let mut result = BTreeMap::new(); let deployment = self.get_current_deployment()?; let network_key = deployment.deployment.order.network.key.clone(); - // Build a key->address map from inputs/outputs that reflects current state let mut resolved = HashMap::new(); for io in deployment .deployment @@ -129,8 +127,7 @@ impl DotrainOrderGui { } } - // Emit only the tokens configured for selection in this deployment - if let Some(st) = GuiCfg::parse_select_tokens( + if let Some(st) = OrderBuilderCfg::parse_select_tokens( self.dotrain_order.dotrain_yaml().documents, &self.selected_deployment, )? { @@ -149,8 +146,6 @@ impl DotrainOrderGui { result }; - // Convert vault_ids to "{io_type}_{index}" keys where index matches IO position - // in the order's inputs/outputs arrays for deterministic reconstruction. let deployment = self.get_current_deployment()?; let mut vault_ids = BTreeMap::new(); for (i, input) in deployment.deployment.order.inputs.iter().enumerate() { @@ -164,7 +159,6 @@ impl DotrainOrderGui { vault_ids.insert(key, value); } - // Convert field values to ValueCfg with normalized value and optional preset ID let field_values = self .field_values .iter() @@ -173,7 +167,6 @@ impl DotrainOrderGui { Ok(( k.clone(), ValueCfg { - // Preserve preset linkage if applicable; otherwise use the binding key id: if v.is_preset { v.value.clone() } else { @@ -184,9 +177,9 @@ impl DotrainOrderGui { }, )) }) - .collect::>()?; + .collect::>()?; - Ok(DotrainGuiStateV1 { + Ok(OrderBuilderStateV1 { dotrain_hash, field_values, deposits, @@ -196,50 +189,20 @@ impl DotrainOrderGui { }) } - /// Exports the complete GUI state as a compressed, encoded string. - /// - /// Serializes all current configuration including field values, deposits, - /// selected tokens, and vault IDs into a compact format for persistence - /// or sharing. The output is gzipped and base64-encoded. - /// - /// ## State Contents - /// - /// - Field values with preset information - /// - Deposit amounts with preset references - /// - Selected token configurations - /// - Vault ID assignments - /// - Configuration hash for validation - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.serializeState(); - /// if (result.error) { - /// console.error("Serialization error:", result.error.readableMsg); - /// return; - /// } - /// const state = result.value; - /// // Do something with the state - /// ``` - #[wasm_export( - js_name = "serializeState", - unchecked_return_type = "string", - return_description = "Compressed, base64-encoded state data" - )] - pub fn serialize_state(&self) -> Result { + pub fn serialize_state(&self) -> Result { let mut field_values = BTreeMap::new(); for (k, v) in self.field_values.iter() { let preset = if v.is_preset { - let presets = GuiCfg::parse_field_presets( + let presets = OrderBuilderCfg::parse_field_presets( self.dotrain_order.dotrain_yaml().documents.clone(), &self.selected_deployment, k, )? - .ok_or(GuiError::BindingHasNoPresets(k.clone()))?; + .ok_or(RaindexOrderBuilderError::BindingHasNoPresets(k.clone()))?; presets .iter() .find(|preset| preset.id == v.value) - .ok_or(GuiError::InvalidPreset)? + .ok_or(RaindexOrderBuilderError::InvalidPreset)? .clone() } else { Self::create_preset(v, String::default()) @@ -254,7 +217,7 @@ impl DotrainOrderGui { } let mut select_tokens: BTreeMap = BTreeMap::new(); - if let Some(st) = GuiCfg::parse_select_tokens( + if let Some(st) = OrderBuilderCfg::parse_select_tokens( self.dotrain_order.dotrain_yaml().documents.clone(), &self.selected_deployment, )? { @@ -285,7 +248,7 @@ impl DotrainOrderGui { false, )?); - let state = SerializedGuiState { + let state = SerializedBuilderState { field_values: field_values.clone(), deposits: deposits.clone(), select_tokens: select_tokens.clone(), @@ -302,62 +265,28 @@ impl DotrainOrderGui { Ok(URL_SAFE.encode(compressed)) } - /// Restores a GUI instance from previously serialized state. - /// - /// Creates a new GUI instance with all configuration restored from a saved state. - /// The provided dotrain should be the full template that was used when - /// serializing the state. Hash validation is performed against its - /// trimmed-for-deployment form to keep the template user-agnostic. - /// - /// ## Security - /// - /// The function validates that the dotrain content hasn't changed by comparing - /// hashes. This prevents state injection attacks and ensures consistency. - /// - /// ## Examples - /// - /// ```javascript - /// const result = await DotrainOrderGui.newFromState(dotrainYaml, savedState); - /// if (result.error) { - /// console.error("Restore failed:", result.error.readableMsg); - /// return; - /// } - /// const gui = result.value; - /// // Do something with the gui - /// ``` - #[wasm_export( - js_name = "newFromState", - preserve_js_class, - return_description = "Fully restored GUI instance" - )] pub async fn new_from_state( - #[wasm_export(param_description = "Must match the original dotrain content exactly")] dotrain: String, - #[wasm_export( - param_description = "Optional additional YAML configuration strings to merge with the frontmatter" - )] settings: Option>, - #[wasm_export(param_description = "Previously serialized state string")] serialized: String, - #[wasm_export(param_description = "Optional callback for future state changes")] - state_update_callback: Option, - ) -> Result { + serialized: String, + ) -> Result { let compressed = URL_SAFE.decode(serialized)?; let mut decoder = GzDecoder::new(&compressed[..]); let mut bytes = Vec::new(); decoder.read_to_end(&mut bytes)?; - let state: SerializedGuiState = bincode::deserialize(&bytes)?; + let state: SerializedBuilderState = bincode::deserialize(&bytes)?; let dotrain_order = DotrainOrder::create_with_profile( dotrain.clone(), settings, - ContextProfile::gui(state.selected_deployment.clone()), + ContextProfile::builder(state.selected_deployment.clone()), ) .await?; - let original_dotrain_hash = DotrainOrderGui::compute_state_hash(&dotrain_order)?; + let original_dotrain_hash = RaindexOrderBuilder::compute_state_hash(&dotrain_order)?; if original_dotrain_hash != state.dotrain_hash { - return Err(GuiError::DotrainMismatch); + return Err(RaindexOrderBuilderError::DotrainMismatch); } let field_values = state @@ -372,34 +301,33 @@ impl DotrainOrderGui { .map(|(k, v)| (k, Self::preset_to_pair_value(v))) .collect::>(); - let dotrain_order_gui = DotrainOrderGui { + let builder = RaindexOrderBuilder { dotrain_order, field_values, deposits, selected_deployment: state.selected_deployment.clone(), dotrain_hash: original_dotrain_hash, - state_update_callback, }; - let deployment_select_tokens = GuiCfg::parse_select_tokens( - dotrain_order_gui.dotrain_order.dotrain_yaml().documents, + let deployment_select_tokens = OrderBuilderCfg::parse_select_tokens( + builder.dotrain_order.dotrain_yaml().documents, &state.selected_deployment, )?; for (key, token) in state.select_tokens { let select_tokens = deployment_select_tokens .as_ref() - .ok_or(GuiError::SelectTokensNotSet)?; + .ok_or(RaindexOrderBuilderError::SelectTokensNotSet)?; if !select_tokens.iter().any(|token| token.key == key) { - return Err(GuiError::TokenNotInSelectTokens(key)); + return Err(RaindexOrderBuilderError::TokenNotInSelectTokens(key)); } - if dotrain_order_gui.is_select_token_set(key.clone())? { + if builder.is_select_token_set(key.clone())? { TokenCfg::remove_record_from_yaml( - dotrain_order_gui.dotrain_order.orderbook_yaml().documents, + builder.dotrain_order.orderbook_yaml().documents, &key, )?; } TokenCfg::add_record_to_yaml( - dotrain_order_gui.dotrain_order.orderbook_yaml().documents, + builder.dotrain_order.orderbook_yaml().documents, &key, &token.network.key, &token.address.to_string(), @@ -410,102 +338,21 @@ impl DotrainOrderGui { } let order_key = DeploymentCfg::parse_order_key( - dotrain_order_gui.dotrain_order.dotrain_yaml().documents, + builder.dotrain_order.dotrain_yaml().documents, &state.selected_deployment, )?; for ((is_input, index), vault_id) in state.vault_ids { - dotrain_order_gui + builder .dotrain_order .dotrain_yaml() - .get_order_for_gui_deployment(&order_key, &state.selected_deployment) + .get_order_for_builder_deployment(&order_key, &state.selected_deployment) .and_then(|mut order| order.update_vault_id(is_input, index, vault_id))?; } - Ok(dotrain_order_gui) - } - - /// Manually triggers the state update callback. - /// - /// Calls the registered state update callback with the current serialized state. - /// This is typically called automatically after state-changing operations, - /// but can be triggered manually if needed. - /// - /// ## Examples - /// - /// ```javascript - /// // Manual state update trigger - /// const result = gui.executeStateUpdateCallback(); - /// if (result.error) { - /// console.error("Callback error:", result.error.readableMsg); - /// } - /// ``` - #[wasm_export( - js_name = "executeStateUpdateCallback", - unchecked_return_type = "void", - return_description = "Callback executed successfully or no callback registered" - )] - pub fn execute_state_update_callback(&self) -> Result<(), GuiError> { - if let Some(callback) = &self.state_update_callback { - let state = to_js_value(&self.serialize_state()?)?; - callback.call1(&JsValue::UNDEFINED, &state).map_err(|e| { - GuiError::JsError(format!("Failed to execute state update callback: {:?}", e)) - })?; - } - Ok(()) + Ok(builder) } - /// Gets comprehensive configuration for complete UI initialization. - /// - /// Provides all configuration data needed to build a complete order interface, - /// organized by requirement type for progressive UI construction. - /// - /// ## Configuration Structure - /// - /// - `fieldDefinitionsWithoutDefaults` - Required fields needing user input - /// - `fieldDefinitionsWithDefaults` - Optional fields with fallback values - /// - `deposits` - Deposit configurations with tokens and presets - /// - `orderInputs` - Input token configurations - /// - `orderOutputs` - Output token configurations - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getAllGuiConfig(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// const config = result.value; - /// - /// // Build required fields section - /// config.fieldDefinitionsWithoutDefaults.forEach(field => { - /// // Do something with the fields - /// }); - /// - /// // Build optional fields section - /// if (config.fieldDefinitionsWithDefaults.length > 0) { - /// // Do something with the fields - /// } - /// - /// // Build deposits section - /// config.deposits.forEach(deposit => { - /// // Do something with the deposits - /// }); - /// - /// // Show order preview - /// config.orderInputs.forEach(input => { - /// // Do something with the order inputs - /// }); - /// config.orderOutputs.forEach(output => { - /// // Do something with the order outputs - /// }); - /// ``` - #[wasm_export( - js_name = "getAllGuiConfig", - unchecked_return_type = "AllGuiConfig", - return_description = "Complete configuration package" - )] - pub fn get_all_gui_config(&self) -> Result { + pub fn get_all_builder_config(&self) -> Result { let deployment = self.get_current_deployment()?; let field_definitions_without_defaults = self.get_all_field_definitions(Some(false))?; @@ -514,7 +361,7 @@ impl DotrainOrderGui { let order_inputs = deployment.deployment.order.inputs.clone(); let order_outputs = deployment.deployment.order.outputs.clone(); - Ok(AllGuiConfig { + Ok(AllBuilderConfig { field_definitions_without_defaults, field_definitions_with_defaults, deposits, @@ -522,9 +369,10 @@ impl DotrainOrderGui { order_outputs, }) } -} -impl DotrainOrderGui { - pub fn compute_state_hash(dotrain_order: &DotrainOrder) -> Result { + + pub fn compute_state_hash( + dotrain_order: &DotrainOrder, + ) -> Result { let yaml = emitter::emit_documents(&dotrain_order.dotrain_yaml().documents)?; let rain_document = RainDocument::create(dotrain_order.dotrain()?, None, None, None); @@ -541,23 +389,21 @@ impl DotrainOrderGui { #[cfg(test)] mod tests { use super::*; - use crate::gui::{ + use crate::dotrain::RainDocument; + use crate::dotrain_order::DotrainOrder; + use crate::raindex_order_builder::{ field_values::FieldValue, - tests::{get_yaml, initialize_gui_with_select_tokens}, + tests::{get_yaml, initialize_builder_with_select_tokens}, }; use alloy::primitives::{Address, U256}; - use js_sys::{eval, Reflect}; use rain_orderbook_app_settings::{ network::NetworkCfg, order::VaultType, yaml::YamlParsableHash, }; - use rain_orderbook_common::dotrain::RainDocument; - use rain_orderbook_common::dotrain_order::DotrainOrder; use std::str::FromStr; - use wasm_bindgen_test::wasm_bindgen_test; - const SERIALIZED_STATE: &str = "H4sIAAAAAAAA_21QXWvCMBRt3NgY7EkGexrsByy0aXWzwh4Ev4qgKH6AL6JttNKY1Bqx4p_wJ2v1pmLxPtxzbs7JvTfJadd4A5wtubfkC0w0FU-AxDCyJhPBgaGlTJEXQCkCyq1H3R4776t3qDZiRTGncieiQN37AvSlDMu6zoQ7Zb7YyHLJKBX1KHTxNmKHxIGSjNToWr_5ATRfGMbHTEJ59ApyP9nh20LPqm61zy_Jabe425akI4hto6xqpqpp2z9A4-pv2Cn8tQbxvLGddTvDRX3tBGMyao6Z4xV7vus4k8iq9Oq7_0_1F5RRV-JLU-zRkIn9inJ5AjfkymPKAQAA"; + const SERIALIZED_STATE: &str = "H4sIAAAAAAAA_2NigABOKJ2UmZeSmZeuawjlMzAwQ2lDAwN0RUaMUAEDBjgLxmCD0iX52al5xthMw64SlccD5RXn56bq5qWWlOcXZcP0yULpjJKSAit9_Zz85MScjPziEisLAwtT_aKCZN3SopxqkApGEMkIs9o1xEMEyhQyCauYgEYwCjGyQ6VDQG5QMGZkgfG9_YwZGJjgnkFzuyHcCkNLS5ArUWSN4LJGlpY6UGZKYKlbVV5aVVJeUl5mYUlyQaJZcGVgVmmuhZNBVmSxQYRFortnaLmPsWGyrTgsLFJzUpNLdMGG6qakFuTkV-am5pUAADgqxtfKAQAA"; - fn encode_state(state: &SerializedGuiState) -> String { + fn encode_state(state: &SerializedBuilderState) -> String { let bytes = bincode::serialize(state).unwrap(); let mut encoder = GzEncoder::new(Vec::new(), Compression::default()); encoder.write_all(&bytes).unwrap(); @@ -565,10 +411,10 @@ mod tests { URL_SAFE.encode(compressed) } - async fn configured_gui() -> DotrainOrderGui { - let mut gui = initialize_gui_with_select_tokens().await; + async fn configured_builder() -> RaindexOrderBuilder { + let mut builder = initialize_builder_with_select_tokens().await; - gui.add_record_to_yaml( + builder.add_record_to_yaml( "token3".to_string(), "some-network".to_string(), "0x1234567890123456789012345678901234567890".to_string(), @@ -576,53 +422,60 @@ mod tests { "Token 3".to_string(), "TKN3".to_string(), ); - gui.set_deposit("token3".to_string(), "100".to_string()) + builder + .set_deposit("token3".to_string(), "100".to_string()) .await .unwrap(); - gui.set_field_value("binding-1".to_string(), "100".to_string()) + builder + .set_field_value("binding-1".to_string(), "100".to_string()) .unwrap(); - gui.set_field_value("binding-2".to_string(), "0".to_string()) + builder + .set_field_value("binding-2".to_string(), "0".to_string()) + .unwrap(); + builder + .set_vault_id( + VaultType::Input, + "token1".to_string(), + Some("199".to_string()), + ) + .unwrap(); + builder + .set_vault_id( + VaultType::Output, + "token2".to_string(), + Some("299".to_string()), + ) .unwrap(); - gui.set_vault_id( - VaultType::Input, - "token1".to_string(), - Some("199".to_string()), - ) - .unwrap(); - gui.set_vault_id( - VaultType::Output, - "token2".to_string(), - Some("299".to_string()), - ) - .unwrap(); - gui + builder } - #[wasm_bindgen_test] + #[tokio::test] async fn test_compute_state_hash_changes_on_content_change() { let dotrain = get_yaml(); let order1 = DotrainOrder::create(dotrain.clone(), None).await.unwrap(); - let original_hash = DotrainOrderGui::compute_state_hash(&order1).unwrap(); + let original_hash = RaindexOrderBuilder::compute_state_hash(&order1).unwrap(); let modified = dotrain.replace("Select token deployment", "Select token deployment v2"); let order2 = DotrainOrder::create(modified, None).await.unwrap(); - let modified_hash = DotrainOrderGui::compute_state_hash(&order2).unwrap(); + let modified_hash = RaindexOrderBuilder::compute_state_hash(&order2).unwrap(); assert_ne!(original_hash, modified_hash); let order3 = DotrainOrder::create(get_yaml(), None).await.unwrap(); - let repeated_hash = DotrainOrderGui::compute_state_hash(&order3).unwrap(); + let repeated_hash = RaindexOrderBuilder::compute_state_hash(&order3).unwrap(); assert_eq!(original_hash, repeated_hash); } - #[wasm_bindgen_test] - async fn test_generate_dotrain_gui_state_instance_v1_contents() { - let gui = configured_gui().await; - let state = gui.generate_dotrain_gui_state_instance_v1().unwrap(); + #[tokio::test] + async fn test_generate_dotrain_builder_state_instance_v1_contents() { + let builder = configured_builder().await; + let state = builder + .generate_dotrain_builder_state_instance_v1() + .unwrap(); - let trimmed = gui + let trimmed = builder .dotrain_order - .generate_dotrain_for_deployment(&gui.selected_deployment) + .generate_dotrain_for_deployment(&builder.selected_deployment) .unwrap(); let expected_hash = DotrainSourceV1(trimmed).hash(); assert_eq!(state.dotrain_hash, expected_hash); @@ -651,25 +504,25 @@ mod tests { ); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_serialize_state() { - let gui = configured_gui().await; - let state = gui.serialize_state().unwrap(); + let builder = configured_builder().await; + let state = builder.serialize_state().unwrap(); assert!(!state.is_empty()); assert_eq!(state, SERIALIZED_STATE); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_new_from_state() { - let gui = - DotrainOrderGui::new_from_state(get_yaml(), None, SERIALIZED_STATE.to_string(), None) + let builder = + RaindexOrderBuilder::new_from_state(get_yaml(), None, SERIALIZED_STATE.to_string()) .await .unwrap(); - assert!(gui.is_select_token_set("token3".to_string()).unwrap()); - assert_eq!(gui.get_deposits().unwrap()[0].amount, "100"); + assert!(builder.is_select_token_set("token3".to_string()).unwrap()); + assert_eq!(builder.get_deposits().unwrap()[0].amount, "100"); assert_eq!( - gui.get_field_value("binding-1".to_string()).unwrap(), + builder.get_field_value("binding-1".to_string()).unwrap(), FieldValue { field: "binding-1".to_string(), value: "100".to_string(), @@ -677,14 +530,14 @@ mod tests { } ); assert_eq!( - gui.get_field_value("binding-2".to_string()).unwrap(), + builder.get_field_value("binding-2".to_string()).unwrap(), FieldValue { field: "binding-2".to_string(), value: "0".to_string(), is_preset: true, } ); - let vault_ids = gui.get_vault_ids().unwrap().0; + let vault_ids = builder.get_vault_ids().unwrap().0; assert_eq!( vault_ids.get("input").unwrap()["token1"], Some(U256::from(199)) @@ -695,7 +548,7 @@ mod tests { ); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_new_from_state_invalid_dotrain() { let dotrain = r#" version: 5 @@ -735,7 +588,7 @@ mod tests { select-token-deployment: order: test scenario: test - gui: + builder: name: Test description: Fixed limit order deployments: @@ -751,50 +604,52 @@ mod tests { #test "#; - let err = DotrainOrderGui::new_from_state( + let err = RaindexOrderBuilder::new_from_state( dotrain.to_string(), None, SERIALIZED_STATE.to_string(), - None, ) .await .unwrap_err(); - assert_eq!(err.to_string(), GuiError::DotrainMismatch.to_string()); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::DotrainMismatch.to_string() + ); assert_eq!( err.to_readable_msg(), "There was a mismatch in the dotrain configuration. Please check your YAML configuration for consistency." ); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_new_from_state_rejects_unknown_select_token_key() { let dotrain = get_yaml(); - let documents = DotrainOrderGui::get_yaml_documents(&dotrain, None).unwrap(); + let documents = RaindexOrderBuilder::get_yaml_documents(&dotrain, None).unwrap(); let token = TokenCfg::parse_from_yaml(documents.clone(), "token1", None).unwrap(); let dotrain_order = DotrainOrder::create(dotrain.clone(), None).await.unwrap(); - let serialized_state = encode_state(&SerializedGuiState { + let serialized_state = encode_state(&SerializedBuilderState { field_values: BTreeMap::new(), deposits: BTreeMap::new(), select_tokens: BTreeMap::from([("token1".to_string(), token)]), vault_ids: BTreeMap::new(), - dotrain_hash: DotrainOrderGui::compute_state_hash(&dotrain_order).unwrap(), + dotrain_hash: RaindexOrderBuilder::compute_state_hash(&dotrain_order).unwrap(), selected_deployment: "select-token-deployment".to_string(), }); - let err = DotrainOrderGui::new_from_state(dotrain, None, serialized_state, None) + let err = RaindexOrderBuilder::new_from_state(dotrain, None, serialized_state) .await .unwrap_err(); assert_eq!( err.to_string(), - GuiError::TokenNotInSelectTokens("token1".to_string()).to_string() + RaindexOrderBuilderError::TokenNotInSelectTokens("token1".to_string()).to_string() ); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_new_from_state_replaces_existing_select_token_record() { let dotrain = get_yaml(); - let documents = DotrainOrderGui::get_yaml_documents(&dotrain, None).unwrap(); + let documents = RaindexOrderBuilder::get_yaml_documents(&dotrain, None).unwrap(); TokenCfg::add_record_to_yaml( documents.clone(), "token3", @@ -831,25 +686,24 @@ mod tests { let dotrain_order = DotrainOrder::create(dotrain_with_existing_token.clone(), None) .await .unwrap(); - let serialized_state = encode_state(&SerializedGuiState { + let serialized_state = encode_state(&SerializedBuilderState { field_values: BTreeMap::new(), deposits: BTreeMap::new(), select_tokens: BTreeMap::from([("token3".to_string(), replacement_token.clone())]), vault_ids: BTreeMap::new(), - dotrain_hash: DotrainOrderGui::compute_state_hash(&dotrain_order).unwrap(), + dotrain_hash: RaindexOrderBuilder::compute_state_hash(&dotrain_order).unwrap(), selected_deployment: "select-token-deployment".to_string(), }); - let gui = DotrainOrderGui::new_from_state( + let builder = RaindexOrderBuilder::new_from_state( dotrain_with_existing_token, None, serialized_state, - None, ) .await .unwrap(); - let restored_token = gui + let restored_token = builder .dotrain_order .orderbook_yaml() .get_token("token3") @@ -859,12 +713,12 @@ mod tests { assert_eq!(restored_token.label, replacement_token.label); } - #[wasm_bindgen_test] + #[tokio::test] async fn test_serialize_state_errors_on_missing_preset() { - let gui = initialize_gui_with_select_tokens().await; - let mut gui = gui; + let builder = initialize_builder_with_select_tokens().await; + let mut builder = builder; - gui.field_values.insert( + builder.field_values.insert( "binding-2".to_string(), field_values::PairValue { is_preset: true, @@ -872,80 +726,10 @@ mod tests { }, ); - let err = gui.serialize_state().unwrap_err(); - assert_eq!(err.to_string(), GuiError::InvalidPreset.to_string()); - } - - #[wasm_bindgen_test] - async fn test_execute_state_update_callback_noop_without_callback() { - let gui = initialize_gui_with_select_tokens().await; - assert!(gui.execute_state_update_callback().is_ok()); - } - - #[wasm_bindgen_test] - async fn test_execute_state_update_callback() { - eval( - r#" - globalThis.callbackCalled = false; - globalThis.receivedState = null; - globalThis.testCallback = function(state) { - globalThis.callbackCalled = true; - globalThis.receivedState = state; - }; - "#, - ) - .unwrap(); - - let global = js_sys::global(); - let callback_js = Reflect::get(&global, &JsValue::from_str("testCallback")) - .expect("should have testCallback function on globalThis") - .dyn_into::() - .expect("testCallback should be a function"); - - let mut gui = DotrainOrderGui::new_with_deployment( - get_yaml(), - None, - "some-deployment".to_string(), - Some(callback_js.clone()), - ) - .await - .unwrap(); - - let callback_called = Reflect::get(&global, &JsValue::from_str("callbackCalled")) - .expect("should have callbackCalled flag on globalThis"); - assert_eq!(callback_called, JsValue::from_bool(false)); - - gui.set_deposit("token1".to_string(), "100".to_string()) - .await - .unwrap(); - gui.set_field_value("binding-1".to_string(), "100".to_string()) - .unwrap(); - gui.set_field_value("binding-2".to_string(), "582.1".to_string()) - .unwrap(); - gui.set_vault_id( - VaultType::Input, - "token1".to_string(), - Some("199".to_string()), - ) - .unwrap(); - gui.set_vault_id( - VaultType::Output, - "token2".to_string(), - Some("299".to_string()), - ) - .unwrap(); - - let callback_called = Reflect::get(&global, &JsValue::from_str("callbackCalled")) - .expect("should have callbackCalled flag on globalThis"); - assert_eq!(callback_called, JsValue::from_bool(true)); - - let received_state_js = Reflect::get(&global, &JsValue::from_str("receivedState")) - .expect("should have receivedState on globalThis"); - assert!(received_state_js.is_string()); - let received_state_rust: String = received_state_js.as_string().unwrap(); - assert!(!received_state_rust.is_empty()); - - let expected_state = gui.serialize_state().unwrap(); - assert_eq!(received_state_rust, expected_state); + let err = builder.serialize_state().unwrap_err(); + assert_eq!( + err.to_string(), + RaindexOrderBuilderError::InvalidPreset.to_string() + ); } } diff --git a/crates/js_api/src/gui/validation.rs b/crates/common/src/raindex_order_builder/validation.rs similarity index 84% rename from crates/js_api/src/gui/validation.rs rename to crates/common/src/raindex_order_builder/validation.rs index e02c4f4a25..971a6ea5b6 100644 --- a/crates/js_api/src/gui/validation.rs +++ b/crates/common/src/raindex_order_builder/validation.rs @@ -1,9 +1,9 @@ use rain_math_float::{Float, FloatError}; -use rain_orderbook_app_settings::gui::{DepositValidationCfg, FieldValueValidationCfg}; +use rain_orderbook_app_settings::order_builder::{DepositValidationCfg, FieldValueValidationCfg}; use thiserror::Error; #[derive(Error, Debug)] -pub enum GuiValidationError { +pub enum BuilderValidationError { #[error("The {name} field contains an invalid number: '{value}'. Please enter a valid numeric value.")] InvalidNumber { name: String, value: String }, @@ -64,7 +64,7 @@ pub fn validate_field_value( field_name: &str, value: &str, validation: &FieldValueValidationCfg, -) -> Result<(), GuiValidationError> { +) -> Result<(), BuilderValidationError> { match validation { FieldValueValidationCfg::Number { minimum, @@ -91,7 +91,7 @@ pub fn validate_deposit_amount( token_name: &str, amount: &str, validation: &DepositValidationCfg, -) -> Result<(), GuiValidationError> { +) -> Result<(), BuilderValidationError> { validate_number( token_name, amount, @@ -109,9 +109,9 @@ fn validate_number( exclusive_minimum: &Option, maximum: &Option, exclusive_maximum: &Option, -) -> Result<(), GuiValidationError> { +) -> Result<(), BuilderValidationError> { if value.is_empty() { - return Err(GuiValidationError::InvalidNumber { + return Err(BuilderValidationError::InvalidNumber { name: name.to_string(), value: value.to_string(), }); @@ -120,9 +120,8 @@ fn validate_number( let float_value = Float::parse(value.to_string())?; let zero = Float::parse("0".to_string())?; - // Reject negative numbers if float_value.lt(zero)? { - return Err(GuiValidationError::InvalidNumber { + return Err(BuilderValidationError::InvalidNumber { name: name.to_string(), value: value.to_string(), }); @@ -131,7 +130,7 @@ fn validate_number( if let Some(min) = minimum { let float_min = Float::parse(min.clone())?; if float_value.lt(float_min)? { - return Err(GuiValidationError::BelowMinimum { + return Err(BuilderValidationError::BelowMinimum { name: name.to_string(), value: value.to_string(), minimum: min.clone(), @@ -142,7 +141,7 @@ fn validate_number( if let Some(exclusive_min) = exclusive_minimum { let exclusive_min_float = Float::parse(exclusive_min.clone())?; if float_value.lte(exclusive_min_float)? { - return Err(GuiValidationError::BelowExclusiveMinimum { + return Err(BuilderValidationError::BelowExclusiveMinimum { name: name.to_string(), value: value.to_string(), exclusive_minimum: exclusive_min.clone(), @@ -153,7 +152,7 @@ fn validate_number( if let Some(max) = maximum { let max_float = Float::parse(max.clone())?; if float_value.gt(max_float)? { - return Err(GuiValidationError::AboveMaximum { + return Err(BuilderValidationError::AboveMaximum { name: name.to_string(), value: value.to_string(), maximum: max.clone(), @@ -164,7 +163,7 @@ fn validate_number( if let Some(exclusive_max) = exclusive_maximum { let exclusive_max_float = Float::parse(exclusive_max.clone())?; if float_value.gte(exclusive_max_float)? { - return Err(GuiValidationError::AboveExclusiveMaximum { + return Err(BuilderValidationError::AboveExclusiveMaximum { name: name.to_string(), value: value.to_string(), exclusive_maximum: exclusive_max.clone(), @@ -172,8 +171,6 @@ fn validate_number( } } - // TODO: Implement multiple_of validation later on - Ok(()) } @@ -182,13 +179,13 @@ fn validate_string( value: &str, min_length: &Option, max_length: &Option, -) -> Result<(), GuiValidationError> { +) -> Result<(), BuilderValidationError> { let trimmed_value = value.trim(); let length = trimmed_value.len() as u32; if let Some(min) = min_length { if length < *min { - return Err(GuiValidationError::StringTooShort { + return Err(BuilderValidationError::StringTooShort { name: name.to_string(), length, minimum: *min, @@ -198,7 +195,7 @@ fn validate_string( if let Some(max) = max_length { if length > *max { - return Err(GuiValidationError::StringTooLong { + return Err(BuilderValidationError::StringTooLong { name: name.to_string(), length, maximum: *max, @@ -209,10 +206,10 @@ fn validate_string( Ok(()) } -fn validate_boolean(name: &str, value: &str) -> Result<(), GuiValidationError> { +fn validate_boolean(name: &str, value: &str) -> Result<(), BuilderValidationError> { match value { "true" | "false" => Ok(()), - _ => Err(GuiValidationError::InvalidBoolean { + _ => Err(BuilderValidationError::InvalidBoolean { name: name.to_string(), value: value.to_string(), }), @@ -222,10 +219,9 @@ fn validate_boolean(name: &str, value: &str) -> Result<(), GuiValidationError> { #[cfg(test)] mod tests { use super::*; - use rain_orderbook_app_settings::gui::FieldValueValidationCfg; - use wasm_bindgen_test::wasm_bindgen_test; + use rain_orderbook_app_settings::order_builder::FieldValueValidationCfg; - #[wasm_bindgen_test] + #[test] fn test_validate_number_minimum() { let result = validate_number( "Test Field", @@ -236,7 +232,7 @@ mod tests { &None, ); match &result { - Err(GuiValidationError::BelowMinimum { + Err(BuilderValidationError::BelowMinimum { name, value, minimum, @@ -269,11 +265,11 @@ mod tests { assert!(result.is_ok()); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_exclusive_minimum() { let result = validate_number("Price", "10", &None, &Some("10".to_string()), &None, &None); match &result { - Err(GuiValidationError::BelowExclusiveMinimum { + Err(BuilderValidationError::BelowExclusiveMinimum { name, value, exclusive_minimum, @@ -296,7 +292,7 @@ mod tests { assert!(result.is_ok()); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_maximum() { let result = validate_number( "Amount", @@ -307,7 +303,7 @@ mod tests { &None, ); match &result { - Err(GuiValidationError::AboveMaximum { + Err(BuilderValidationError::AboveMaximum { name, value, maximum, @@ -340,7 +336,7 @@ mod tests { assert!(result.is_ok()); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_exclusive_maximum() { let result = validate_number( "Token Amount", @@ -351,7 +347,7 @@ mod tests { &Some("100".to_string()), ); match &result { - Err(GuiValidationError::AboveExclusiveMaximum { + Err(BuilderValidationError::AboveExclusiveMaximum { name, value, exclusive_maximum, @@ -374,7 +370,7 @@ mod tests { assert!(result.is_ok()); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_combined_constraints() { let result = validate_number( "Complex Field", @@ -396,7 +392,7 @@ mod tests { ); assert!(matches!( result, - Err(GuiValidationError::BelowMinimum { .. }) + Err(BuilderValidationError::BelowMinimum { .. }) )); let result = validate_number( @@ -409,11 +405,11 @@ mod tests { ); assert!(matches!( result, - Err(GuiValidationError::AboveMaximum { .. }) + Err(BuilderValidationError::AboveMaximum { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_parsing() { let result = validate_number("Test Field", "100.5", &None, &None, &None, &None); assert!(result.is_ok()); @@ -432,19 +428,25 @@ mod tests { assert!(result.is_ok()); let result = validate_number("Test Field", "not a number", &None, &None, &None, &None); - assert!(matches!(result, Err(GuiValidationError::FloatError(..)))); + assert!(matches!( + result, + Err(BuilderValidationError::FloatError(..)) + )); let result = validate_number("Test Field", "12.34.56", &None, &None, &None, &None); - assert!(matches!(result, Err(GuiValidationError::FloatError(..)))); + assert!(matches!( + result, + Err(BuilderValidationError::FloatError(..)) + )); let result = validate_number("Test Field", "", &None, &None, &None, &None); assert!(matches!( result, - Err(GuiValidationError::InvalidNumber { .. }) + Err(BuilderValidationError::InvalidNumber { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_decimals() { let result = validate_number("USDC Amount", "100.123456", &None, &None, &None, &None); assert!(result.is_ok()); @@ -463,7 +465,7 @@ mod tests { assert!(result.is_ok()); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_edge_cases() { let result = validate_number("Amount", "0", &None, &None, &None, &None); assert!(result.is_ok()); @@ -485,32 +487,32 @@ mod tests { assert!(result.is_ok()); } - #[wasm_bindgen_test] + #[test] fn test_validate_number_rejects_negative() { let result = validate_number("Amount", "-1", &None, &None, &None, &None); assert!(matches!( result, - Err(GuiValidationError::InvalidNumber { .. }) + Err(BuilderValidationError::InvalidNumber { .. }) )); let result = validate_number("Amount", "-0.01", &None, &None, &None, &None); assert!(matches!( result, - Err(GuiValidationError::InvalidNumber { .. }) + Err(BuilderValidationError::InvalidNumber { .. }) )); let result = validate_number("Amount", "-100.5", &None, &None, &None, &None); assert!(matches!( result, - Err(GuiValidationError::InvalidNumber { .. }) + Err(BuilderValidationError::InvalidNumber { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_string_length() { let result = validate_string("Username", "hello", &Some(10), &None); match &result { - Err(GuiValidationError::StringTooShort { + Err(BuilderValidationError::StringTooShort { name, length, minimum, @@ -530,7 +532,7 @@ mod tests { let result = validate_string("Description", &"a".repeat(100), &None, &Some(50)); match &result { - Err(GuiValidationError::StringTooLong { + Err(BuilderValidationError::StringTooLong { name, length, maximum, @@ -543,7 +545,7 @@ mod tests { } } - #[wasm_bindgen_test] + #[test] fn test_validate_string_edge_cases() { let result = validate_string("Field", "", &None, &None); assert!(result.is_ok()); @@ -551,7 +553,7 @@ mod tests { let result = validate_string("Field", "", &Some(1), &None); assert!(matches!( result, - Err(GuiValidationError::StringTooShort { .. }) + Err(BuilderValidationError::StringTooShort { .. }) )); let result = validate_string("Field", "12345", &Some(5), &Some(5)); @@ -563,11 +565,11 @@ mod tests { let result = validate_string("Field", "🦀", &Some(5), &None); assert!(matches!( result, - Err(GuiValidationError::StringTooShort { .. }) + Err(BuilderValidationError::StringTooShort { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_string_trimming() { let result = validate_string("Username", " hello ", &Some(3), &Some(10)); assert!(result.is_ok()); @@ -575,7 +577,7 @@ mod tests { let result = validate_string("Username", " hi ", &Some(5), &None); assert!(matches!( result, - Err(GuiValidationError::StringTooShort { .. }) + Err(BuilderValidationError::StringTooShort { .. }) )); let result = validate_string("Username", "\t\nhello world\t\n", &Some(5), &Some(15)); @@ -584,17 +586,17 @@ mod tests { let result = validate_string("Description", " ", &Some(1), &None); assert!(matches!( result, - Err(GuiValidationError::StringTooShort { .. }) + Err(BuilderValidationError::StringTooShort { .. }) )); let result = validate_string("Field", " toolong ", &None, &Some(6)); assert!(matches!( result, - Err(GuiValidationError::StringTooLong { .. }) + Err(BuilderValidationError::StringTooLong { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_boolean() { let result = validate_boolean("Enable Feature", "true"); assert!(result.is_ok()); @@ -604,7 +606,7 @@ mod tests { let result = validate_boolean("Enable Feature", "yes"); match &result { - Err(GuiValidationError::InvalidBoolean { name, value }) => { + Err(BuilderValidationError::InvalidBoolean { name, value }) => { assert_eq!(name, "Enable Feature"); assert_eq!(value, "yes"); } @@ -614,29 +616,29 @@ mod tests { let result = validate_boolean("Enable Feature", "True"); assert!(matches!( result, - Err(GuiValidationError::InvalidBoolean { .. }) + Err(BuilderValidationError::InvalidBoolean { .. }) )); let result = validate_boolean("Enable Feature", "FALSE"); assert!(matches!( result, - Err(GuiValidationError::InvalidBoolean { .. }) + Err(BuilderValidationError::InvalidBoolean { .. }) )); let result = validate_boolean("Enable Feature", "False"); assert!(matches!( result, - Err(GuiValidationError::InvalidBoolean { .. }) + Err(BuilderValidationError::InvalidBoolean { .. }) )); let result = validate_boolean("Enable Feature", ""); assert!(matches!( result, - Err(GuiValidationError::InvalidBoolean { .. }) + Err(BuilderValidationError::InvalidBoolean { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_field_value_number() { let validation = FieldValueValidationCfg::Number { minimum: Some("10".to_string()), @@ -651,17 +653,17 @@ mod tests { let result = validate_field_value("Price Field", "5", &validation); assert!(matches!( result, - Err(GuiValidationError::BelowMinimum { .. }) + Err(BuilderValidationError::BelowMinimum { .. }) )); let result = validate_field_value("Price Field", "102", &validation); assert!(matches!( result, - Err(GuiValidationError::AboveMaximum { .. }) + Err(BuilderValidationError::AboveMaximum { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_field_value_string() { let validation = FieldValueValidationCfg::String { min_length: Some(3), @@ -674,17 +676,17 @@ mod tests { let result = validate_field_value("Name Field", "hi", &validation); assert!(matches!( result, - Err(GuiValidationError::StringTooShort { .. }) + Err(BuilderValidationError::StringTooShort { .. }) )); let result = validate_field_value("Name Field", "hello world!", &validation); assert!(matches!( result, - Err(GuiValidationError::StringTooLong { .. }) + Err(BuilderValidationError::StringTooLong { .. }) )); } - #[wasm_bindgen_test] + #[test] fn test_validate_field_value_boolean() { let validation = FieldValueValidationCfg::Boolean; @@ -697,7 +699,7 @@ mod tests { let result = validate_field_value("Toggle Field", "maybe", &validation); assert!(matches!( result, - Err(GuiValidationError::InvalidBoolean { .. }) + Err(BuilderValidationError::InvalidBoolean { .. }) )); } } diff --git a/crates/common/src/test_helpers.rs b/crates/common/src/test_helpers.rs index 939dea256a..c66f544f6a 100644 --- a/crates/common/src/test_helpers.rs +++ b/crates/common/src/test_helpers.rs @@ -78,8 +78,8 @@ deployments: deployment2: order: order1 scenario: scenario1 -gui: - name: Test gui +builder: + name: Test builder description: Test description short-description: Test short description deployments: diff --git a/crates/js_api/ARCHITECTURE.md b/crates/js_api/ARCHITECTURE.md index 9ff1ed82d6..32eda8cba3 100644 --- a/crates/js_api/ARCHITECTURE.md +++ b/crates/js_api/ARCHITECTURE.md @@ -1,16 +1,16 @@ **Overview** - Purpose: `rain_orderbook_js_api` exposes a single, browser-friendly WebAssembly surface for the Rain Orderbook application. It bridges YAML-based “dotrain” order configuration, on-chain ERC‑20/token metadata, and contract call generation into a typed JavaScript/TypeScript API. - Target: Compiles as a `cdylib` for wasm and is designed to be consumed from JS environments (webapps). All public APIs are exported via `wasm_bindgen_utils` macros and return ergonomic results with rich, user‑readable errors. -- Scope: Includes high-level GUI helpers for interactive order building, a fetchable registry of orders, and low-level helpers for hashing and ABI calldata generation. It re-exports certain sibling crates so their wasm bindings are reachable from a single import. +- Scope: Includes high-level order builder helpers for interactive order building, a fetchable registry of orders, and low-level helpers for hashing and ABI calldata generation. It re-exports certain sibling crates so their wasm bindings are reachable from a single import. **Build & Targets** -- Crate type: `cdylib` (WASM output). Most modules are `#[cfg(target_family = "wasm")]` as they are JS/GUI facing. +- Crate type: `cdylib` (WASM output). Most modules are `#[cfg(target_family = "wasm")]` as they are JS facing. - Key dependencies: `wasm-bindgen-utils`, `alloy` (ABI/primitives), `rain_orderbook_*` crates for app models + on-chain helpers, `tokio` (async), `reqwest` (HTTP, registry), `flate2`/`base64`/`bincode`/`sha2` (state serialization), `strict-yaml-rust` (YAML AST). - TypeScript support: Adds TS definitions for `Address` and `Hex` template literal types and uses `tsify` to describe return/param types of exported structs. **Top-Level Layout** - `src/lib.rs` - - Exposes modules only when targeting wasm: `bindings`, `gui`, `registry`, `yaml`. + - Exposes modules only when targeting wasm: `bindings`, `raindex_order_builder`, `registry`, `yaml`. - Re-exports crates so their wasm bindings are available from this single module: `rain_orderbook_app_settings`, `rain_orderbook_common`, `rain_orderbook_subgraph_client`. - Appends a small TS section defining `Address` and `Hex` template literal types for better typing on the JS side. @@ -24,7 +24,7 @@ **Modules** - `bindings` (src/bindings/mod.rs) - - Purpose: Low-level helpers exposed to JS for hashing and ABI encoding independent of the GUI flow. + - Purpose: Low-level helpers exposed to JS for hashing and ABI encoding independent of the order builder flow. - Key types and exports: - `TakeOrdersCalldata(Bytes)` as an opaque JS type for encoded calldata. - `getOrderHash(order: OrderV4) -> string`: ABI-encodes `OrderV4` and returns `keccak256` with `0x` prefix. @@ -32,15 +32,15 @@ - `keccak256(bytes: Uint8Array) -> string` and `keccak256HexString(hex: string) -> string`. - Errors: `Error::FromHexError` mapped to JS with human-readable message. -- `gui` (src/gui/…) +- `raindex_order_builder` (src/raindex_order_builder/…) - Purpose: High-level, stateful orchestrator for interactive order creation from a dotrain (YAML + Rainlang) configuration. Encapsulates reading config, managing user inputs, querying token metadata, validating fields, and generating contract call data for deployment. - - Core type: `DotrainOrderGui` + - Core type: `RaindexOrderBuilder` - Fields: `dotrain_order` (parsed configuration), `selected_deployment`, `field_values` and `deposits` (with preset tracking), and an optional `state_update_callback` JS function. - Construction: - - `DotrainOrderGui.getDeploymentKeys(dotrain: string) -> string[]` parses `gui.deployments`. - - `DotrainOrderGui.newWithDeployment(dotrain, selectedDeployment, stateUpdateCallback?) -> DotrainOrderGui` validates the deployment and bootstraps a GUI instance. + - `RaindexOrderBuilder.getDeploymentKeys(dotrain: string) -> string[]` parses `builder.deployments`. + - `RaindexOrderBuilder.newWithDeployment(dotrain, selectedDeployment, stateUpdateCallback?) -> RaindexOrderBuilder` validates the deployment and bootstraps an order builder instance. - Config accessors: - - `getGuiConfig() -> GuiCfg`, `getCurrentDeployment() -> GuiDeploymentCfg` (filtered for the active deployment). + - `getBuilderConfig() -> OrderBuilderCfg`, `getCurrentDeployment() -> OrderBuilderDeploymentCfg` (filtered for the active deployment). - `getOrderDetails(dotrain) -> NameAndDescriptionCfg` (static), `getDeploymentDetails(dotrain) -> Map`, `getDeploymentDetail(dotrain, key) -> NameAndDescriptionCfg`. - `getCurrentDeploymentDetails() -> NameAndDescriptionCfg`. - Token metadata: @@ -52,18 +52,18 @@ - Submodules - `field_values.rs` - - User-controlled inputs declared under `gui.deployments[*].fields`. + - User-controlled inputs declared under `builder.deployments[*].fields`. - Setters and getters: - `setFieldValue(binding, value)`: validates (if rules exist), detects preset matches, stores as either preset index or custom value, and triggers the state callback. - `setFieldValues([{field, value}, …])`: batch equivalent. - `unsetFieldValue(binding)`. - `getFieldValue(binding) -> { field, value, isPreset }` expands presets to actual values for display. - `getAllFieldValues() -> FieldValue[]`. - - `getFieldDefinition(binding) -> GuiFieldDefinitionCfg` and `getAllFieldDefinitions(filterDefaults?)` (filter by has default/no default), `getMissingFieldValues()`. + - `getFieldDefinition(binding) -> OrderBuilderFieldDefinitionCfg` and `getAllFieldDefinitions(filterDefaults?)` (filter by has default/no default), `getMissingFieldValues()`. - Validation: delegated to `validation.rs` using YAML-provided rules (Number min/max/exclusive bounds, String min/max length, Boolean exact `"true"|"false"`). Uses `rain_math_float::Float` for precise numeric comparisons. - `deposits.rs` - - User deposit amounts declared under `gui.deployments[*].deposits`. + - User deposit amounts declared under `builder.deployments[*].deposits`. - Helpers: - `getDeposits() -> TokenDeposit[]` expanding presets to actual values and pairing with token addresses. - `setDeposit(tokenKey, amount)` validates per-token rules (min/max/exclusive), detects presets, stores, and triggers state callback. @@ -73,7 +73,7 @@ - `select_tokens.rs` - For deployments that declare `select-tokens`, users supply token contracts at runtime. - Features: - - `getSelectTokens() -> GuiSelectTokensCfg[]` and `checkSelectTokens()`. + - `getSelectTokens() -> OrderBuilderSelectTokensCfg[]` and `checkSelectTokens()`. - `isSelectTokenSet(key) -> boolean`. - `setSelectToken(key, address)` fetches ERC‑20 metadata via RPC (derived from the deployment’s network) and writes token records back into the dotrain YAML; triggers state callback. - `unsetSelectToken(key)` removes previously selected token records. @@ -104,24 +104,24 @@ - `state_management.rs` - End-to-end state persistence and restoration: - `serializeState() -> string`: bincode-serializes a compact state (field values and deposit presets, selected tokens, vault IDs, selected deployment) then gzips and base64-encodes. Also embeds a SHA‑256 of the full dotrain to prevent mismatched restores. - - `DotrainOrderGui.newFromState(dotrain, serialized, callback?) -> DotrainOrderGui`: validates the hash against the provided dotrain, rebuilds internal maps, replays selected tokens and vault IDs back into the YAML/documents, and returns a fully restored instance. + - `RaindexOrderBuilder.newFromState(dotrain, serialized, callback?) -> RaindexOrderBuilder`: validates the hash against the provided dotrain, rebuilds internal maps, replays selected tokens and vault IDs back into the YAML/documents, and returns a fully restored instance. - `executeStateUpdateCallback()`: manually triggers the callback by passing the latest `serializeState()` string. Most mutating methods call this automatically. - - `getAllGuiConfig() -> AllGuiConfig`: returns all front-end relevant config slices grouped for progressive UI building (fields by required/optional, deposits, order inputs/outputs). + - `getAllBuilderConfig() -> AllBuilderConfig`: returns all front-end relevant config slices grouped for progressive UI building (fields by required/optional, deposits, order inputs/outputs). - `validation.rs` - Uniform validation library used by `field_values` and `deposits`: - Numbers: `minimum`, `exclusive-minimum`, `maximum`, `exclusive-maximum`; rejects negatives; precise decimal support via `Float`. - Strings: `min-length`, `max-length` (length measured on trimmed strings). - Booleans: accepts only `"true"` or `"false"`. - - Errors (`GuiValidationError`) carry contextual, user-readable messages; surfaced to JS via `GuiError::ValidationError`. + - Errors (`BuilderValidationError`) carry contextual, user-readable messages; surfaced to JS via `BuilderError::ValidationError`. - - Error type for the GUI: `GuiError` + - Error type for the builder: `BuilderError` - Captures configuration, selection, validation, I/O, chain, and serialization errors. - Provides `to_readable_msg()` with end-user friendly explanations. - Implements conversions to `JsValue` and `WasmEncodedError` for FFI. - `registry` (src/registry.rs) - - Purpose: Fetches a remote registry file that lists one shared settings YAML followed by one or more `.rain` order files. Produces merged dotrain content per order and can directly construct a `DotrainOrderGui` instance. + - Purpose: Fetches a remote registry file that lists one shared settings YAML followed by one or more `.rain` order files. Produces merged dotrain content per order and can directly construct an `RaindexOrderBuilder` instance. - Registry format: - First non-empty line: settings YAML URL (no key) - Subsequent lines: `" "` @@ -131,8 +131,8 @@ - `getOrderKeys()` → keys from `order_urls`. - `getDeploymentDetails(orderKey)` → deployment name/description map for a specific order. - `getOrderbookYaml() -> OrderbookYaml` → returns an `OrderbookYaml` instance from the registry's shared settings YAML for querying tokens, networks, orderbooks, etc. - - `getGui(orderKey, deploymentKey, serializedState?, stateCallback?)` → merge `settings + order`, optionally restore serialized state, and produce a `DotrainOrderGui` instance. - - Errors: `DotrainRegistryError` covers fetch/parse/HTTP/URL issues and wraps `GuiError`. Also returns human-readable messages. + - `getOrderBuilder(orderKey, deploymentKey, serializedState?, stateCallback?)` → merge `settings + order`, optionally restore serialized state, and produce a `RaindexOrderBuilder` instance. + - Errors: `DotrainRegistryError` covers fetch/parse/HTTP/URL issues and wraps `BuilderError`. Also returns human-readable messages. - `yaml` (src/yaml/mod.rs) - Purpose: Wasm-friendly wrapper around orderbook YAML parsing to retrieve configuration objects by address or query token metadata. @@ -143,16 +143,16 @@ - Errors: `OrderbookYamlError` with readable messaging, converted to JS. **External Crates & Interactions** -- `rain_orderbook_app_settings`: typed config model + YAML parsing helpers for GUI sections, deployments, networks, orders, select-tokens, and validation rules. +- `rain_orderbook_app_settings`: typed config model + YAML parsing helpers for order builder sections, deployments, networks, orders, select-tokens, and validation rules. - `rain_orderbook_common`: higher-level order manipulation (compose Rainlang, add order args), ERC‑20 RPC client, transaction helpers, and formatting utilities. - `rain_orderbook_bindings`: generated Solidity bindings for `IOrderBookV5` (e.g., `deposit3`, `multicall`, `takeOrders3`). - `alloy`: ABI encoding/decoding, primitives (`Address`, `Bytes`, `U256`, keccak256), and Solidity type utilities. - `wasm-bindgen-utils`: export macro, JS bridging, `WasmEncodedError` packaging. **Data Flow & Typical Lifecycle** -- From dotrain → GUI → calldata: +- From dotrain → order builder → calldata: - Parse dotrain (frontmatter YAML + Rainlang body) with `DotrainOrder::create`. - - Initialize GUI with a deployment key. + - Initialize order builder with a deployment key. - Optional: select tokens via on-chain metadata, set field values (with validation), set deposit amounts (with validation), and set vault IDs. - Generate approvals if needed, deposits, add order calldata, or a combined multicall. Transaction args include orderbook address and chain ID. - State persistence: @@ -178,17 +178,17 @@ **Edge Cases & Notes** - If YAML is missing token metadata, the crate queries the chain; callers should expect async RPC usage and potential network failures in those code paths. - `WithdrawCalldataResult` exists as a type placeholder; no public generator currently uses it. -- Many GUI methods error if `select-tokens` is configured but tokens are not yet selected, or if required field values/deposits are missing. These error cases surface clear `readable_msg`s. +- Many order builder methods error if `select-tokens` is configured but tokens are not yet selected, or if required field values/deposits are missing. These error cases surface clear `readable_msg`s. - When decimals are absent in YAML, they are fetched on demand before encoding deposits. **How To Use (High-Level)** - Single order flow: - - `const gui = await DotrainOrderGui.newWithDeployment(dotrain, deploymentKey, onStateChanged?)` + - `const builder = await RaindexOrderBuilder.newWithDeployment(dotrain, deploymentKey, onStateChanged?)` - Fill inputs: `setFieldValue`, `setDeposit`, optionally `setSelectToken`, `setVaultId`. - Generate data: `generateAddOrderCalldata` or `generateDepositAndAddOrderCalldatas`; or get the full package from `getDeploymentTransactionArgs(owner)`. - - Persist UI state: read `serializeState()`; restore later with `DotrainOrderGui.newFromState(dotrain, serialized, callback?)`. + - Persist UI state: read `serializeState()`; restore later with `RaindexOrderBuilder.newFromState(dotrain, serialized, callback?)`. - Multiple orders via registry: - - `const registry = await DotrainRegistry.new(registryUrl)` → inspect orders/deployments → `await registry.getGui(orderKey, deploymentKey, serializedState?, onStateChanged?)`. + - `const registry = await DotrainRegistry.new(registryUrl)` → inspect orders/deployments → `await registry.getOrderBuilder(orderKey, deploymentKey, serializedState?, onStateChanged?)`. **Summary** - `rain_orderbook_js_api` is the JS/WASM gateway for building, validating, and deploying Rain Orderbook orders from YAML+Rainlang definitions. It centralizes: YAML parsing and validation, user input state, token selection and metadata, field and deposit validation, vault ID management, transaction calldata generation (approvals, deposits, add order, multicall), registry-driven content fetching, and robust error handling—exposed as a typed, ergonomic TypeScript surface. diff --git a/crates/js_api/src/gui/deposits.rs b/crates/js_api/src/gui/deposits.rs deleted file mode 100644 index e44fcd60a7..0000000000 --- a/crates/js_api/src/gui/deposits.rs +++ /dev/null @@ -1,710 +0,0 @@ -use super::*; -use rain_orderbook_app_settings::gui::GuiDepositCfg; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct TokenDeposit { - pub token: String, - pub amount: String, - #[tsify(type = "string")] - pub address: Address, -} - -impl DotrainOrderGui { - pub fn check_deposits(&self) -> Result<(), GuiError> { - let deployment = self.get_current_deployment()?; - - for deposit in deployment.deposits.iter() { - if deposit.token.is_none() { - return Err(GuiError::MissingDepositToken(deployment.key.clone())); - } - - let token = deposit.token.as_ref().unwrap(); - if !self.deposits.contains_key(&token.key) { - return Err(GuiError::DepositNotSet( - token - .symbol - .clone() - .unwrap_or(token.label.clone().unwrap_or(token.key.clone())), - )); - } - } - Ok(()) - } -} - -#[wasm_export] -impl DotrainOrderGui { - fn get_gui_deposit(&self, key: &str) -> Result { - let deployment = self.get_current_deployment()?; - let gui_deposit = deployment - .deposits - .iter() - .find(|dg| dg.token.as_ref().is_some_and(|t| t.key == *key)) - .ok_or(GuiError::DepositTokenNotFound(key.to_string()))?; - Ok(gui_deposit.clone()) - } - - /// Gets all configured deposit amounts with token information. - /// - /// Returns deposits as token deposits with human-readable amounts and addresses. - /// This combines the stored deposit amounts with token metadata for display and - /// transaction preparation. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getDeposits(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// const [deposit1, deposit2, ...] = result.value; - /// const { - /// // token is the token address - /// token, - /// // amount is the deposit amount - /// amount, - /// // address is the token address - /// } = deposit1; - /// ``` - #[wasm_export( - js_name = "getDeposits", - unchecked_return_type = "TokenDeposit[]", - return_description = "Array of deposits with token details and amounts" - )] - pub fn get_deposits(&self) -> Result, GuiError> { - self.deposits - .iter() - .map(|(key, value)| { - let gui_deposit = self.get_gui_deposit(key)?; - let amount: String = if value.is_preset { - let index = value - .value - .parse::() - .map_err(|_| GuiError::InvalidPreset)?; - gui_deposit - .presets - .as_ref() - .ok_or(GuiError::PresetsNotSet)? - .get(index) - .ok_or(GuiError::InvalidPreset)? - .clone() - } else { - value.value.clone() - }; - - if gui_deposit.token.is_none() { - return Err(GuiError::TokenMustBeSelected(key.clone())); - } - let token = gui_deposit.token.as_ref().unwrap(); - - Ok(TokenDeposit { - token: token.key.clone(), - amount, - address: token.address, - }) - }) - .collect::, GuiError>>() - } - - /// Sets a deposit amount for a specific token. - /// - /// Sets the deposit amount for a token, automatically detecting if the amount - /// matches a preset value. - /// - /// ## Preset Detection - /// - /// If the amount matches a preset value from the configuration, it's stored - /// as a preset reference for efficiency and consistency. - /// - /// ## Examples - /// - /// ```javascript - /// // Set a custom deposit amount - /// const result = await gui.setDeposit("usdc", "1000.50"); - /// if (result.error) { - /// console.error("Deposit error:", result.error.readableMsg); - /// return; - /// } - /// ``` - #[wasm_export(js_name = "setDeposit", unchecked_return_type = "void")] - pub async fn set_deposit( - &mut self, - #[wasm_export(param_description = "Token key from the deposits configuration")] - token: String, - #[wasm_export(param_description = "Human-readable amount (e.g., '100.5')")] amount: String, - ) -> Result<(), GuiError> { - let gui_deposit = self.get_gui_deposit(&token)?; - - if amount.is_empty() { - return Err(GuiError::DepositAmountCannotBeEmpty); - } - - if let Some(validation) = &gui_deposit.validation { - let token_info = self.get_token_info(token.clone()).await?; - validation::validate_deposit_amount(&token_info.name, &amount, validation)?; - } - - let value = match gui_deposit.presets.as_ref() { - Some(presets) => match presets.iter().position(|p| **p == amount) { - Some(index) => field_values::PairValue { - is_preset: true, - value: index.to_string(), - }, - None => field_values::PairValue { - is_preset: false, - value: amount, - }, - }, - None => field_values::PairValue { - is_preset: false, - value: amount, - }, - }; - - self.deposits.insert(token, value); - - self.execute_state_update_callback()?; - Ok(()) - } - - /// Unsets the deposit amount for a specific token. - /// - /// Use this to clear a deposit that's no longer needed. - /// - /// ## Examples - /// - /// ```javascript - /// // Unset a specific token deposit - /// const result = gui.unsetDeposit("usdc"); - /// if (result.error) { - /// console.error("Unset failed:", result.error.readableMsg); - /// return; - /// } - /// ``` - #[wasm_export(js_name = "unsetDeposit", unchecked_return_type = "void")] - pub fn unset_deposit( - &mut self, - #[wasm_export(param_description = "Token key to remove deposit for")] token: String, - ) -> Result<(), GuiError> { - self.deposits.remove(&token); - self.execute_state_update_callback()?; - Ok(()) - } - - /// Gets preset amounts available for a specific deposit token. - /// - /// Returns the preset values configured for this token in the deployment, - /// useful for building quick-select interfaces. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getDepositPresets("usdc"); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// // items are preset amounts - /// const [preset1, preset2, ...] = result.value; - /// ``` - #[wasm_export( - js_name = "getDepositPresets", - unchecked_return_type = "string[]", - return_description = "Array of preset amounts, or empty if no presets" - )] - pub fn get_deposit_presets( - &self, - #[wasm_export(param_description = "Token key from deposits configuration")] key: String, - ) -> Result, GuiError> { - let gui_deposit = self.get_gui_deposit(&key)?; - Ok(gui_deposit.presets.clone().unwrap_or(vec![])) - } - - /// Lists tokens that require deposits but haven't been configured. - /// - /// Returns token keys for deposits that are required by the deployment - /// but haven't been set yet. Use this for validation and user guidance. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getMissingDeposits(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// // items are token keys - /// const [missing1, missing2, ...] = result.value; - /// if (missing.length > 0) { - /// // Do something with the missing tokens - /// } - /// ``` - #[wasm_export( - js_name = "getMissingDeposits", - unchecked_return_type = "string[]", - return_description = "Array of token keys needing deposits" - )] - pub fn get_missing_deposits(&self) -> Result, GuiError> { - let deployment = self.get_current_deployment()?; - let mut missing_deposits = Vec::new(); - - for deposit in deployment.deposits.iter() { - if let Some(token) = &deposit.token { - if !self.deposits.contains_key(&token.key) { - missing_deposits.push(token.key.clone()); - } - } - } - Ok(missing_deposits) - } - - /// Checks if any deposits have been configured. - /// - /// Quick check to determine if the user has started configuring deposits. - /// Useful for showing different UI states or progress indicators. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.hasAnyDeposit(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// const hasDeposits = result.value; - /// if (!hasDeposits) { - /// // Do something - /// } - /// ``` - #[wasm_export( - js_name = "hasAnyDeposit", - unchecked_return_type = "boolean", - return_description = "True if at least one deposit exists" - )] - pub fn has_any_deposit(&self) -> Result { - Ok(!self.deposits.is_empty()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::gui::tests::{ - initialize_gui, initialize_gui_with_select_tokens, initialize_validation_gui, - }; - use crate::gui::validation; - use std::str::FromStr; - use wasm_bindgen_test::wasm_bindgen_test; - - #[wasm_bindgen_test] - async fn test_get_gui_deposit() { - let gui = initialize_gui(None).await; - - let deposit = gui.get_gui_deposit("token1").unwrap(); - assert_eq!(deposit.token.unwrap().key, "token1"); - assert_eq!( - deposit.presets, - Some(vec![ - "0".to_string(), - "10".to_string(), - "100".to_string(), - "1000".to_string(), - "10000".to_string() - ]) - ); - - let err = gui.get_gui_deposit("token2").unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::DepositTokenNotFound("token2".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The deposit token 'token2' was not found in the YAML configuration." - ); - - let gui = initialize_gui_with_select_tokens().await; - let err = gui.get_gui_deposit("token3").unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::DepositTokenNotFound("token3".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The deposit token 'token3' was not found in the YAML configuration." - ); - } - - #[wasm_bindgen_test] - async fn test_get_deposits() { - let mut gui = initialize_gui(None).await; - - gui.set_deposit("token1".to_string(), "999".to_string()) - .await - .unwrap(); - - let deposit = gui.get_deposits().unwrap(); - assert_eq!(deposit.len(), 1); - assert_eq!(deposit[0].token, "token1"); - assert_eq!(deposit[0].amount, "999"); - assert_eq!( - deposit[0].address, - Address::from_str("0xc2132d05d31c914a87c6611c10748aeb04b58e8f").unwrap() - ); - } - - #[wasm_bindgen_test] - async fn test_set_deposit() { - let mut gui = initialize_gui(None).await; - - gui.set_deposit("token1".to_string(), "999".to_string()) - .await - .unwrap(); - - let deposit = gui.get_deposits().unwrap(); - assert_eq!(deposit.len(), 1); - assert_eq!(deposit[0].token, "token1"); - assert_eq!(deposit[0].amount, "999"); - assert_eq!( - deposit[0].address, - Address::from_str("0xc2132d05d31c914a87c6611c10748aeb04b58e8f").unwrap() - ); - - let err = gui - .set_deposit("token1".to_string(), "".to_string()) - .await - .unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::DepositAmountCannotBeEmpty.to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The deposit amount cannot be an empty string. Please set a valid amount." - ); - - let mut gui = initialize_gui_with_select_tokens().await; - let err = gui - .set_deposit("token3".to_string(), "999".to_string()) - .await - .unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::DepositTokenNotFound("token3".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The deposit token 'token3' was not found in the YAML configuration." - ); - } - - #[wasm_bindgen_test] - async fn test_unset_deposit() { - let mut gui = initialize_gui(None).await; - - gui.set_deposit("token1".to_string(), "999".to_string()) - .await - .unwrap(); - let deposit = gui.get_deposits().unwrap(); - assert_eq!(deposit.len(), 1); - - gui.unset_deposit("token1".to_string()).unwrap(); - assert_eq!(gui.get_deposits().unwrap().len(), 0); - } - - #[wasm_bindgen_test] - async fn test_get_deposit_presets() { - let gui = initialize_gui(None).await; - - let presets = gui.get_deposit_presets("token1".to_string()).unwrap(); - assert_eq!( - presets, - vec![ - "0".to_string(), - "10".to_string(), - "100".to_string(), - "1000".to_string(), - "10000".to_string() - ] - ); - } - #[wasm_bindgen_test] - async fn test_get_missing_deposits() { - let gui = initialize_gui(None).await; - - let missing_deposits = gui.get_missing_deposits().unwrap(); - assert_eq!(missing_deposits, vec!["token1".to_string()]); - } - - #[wasm_bindgen_test] - async fn test_has_any_deposit() { - let mut gui = initialize_gui(None).await; - - let has_any_deposit = gui.has_any_deposit().unwrap(); - assert!(!has_any_deposit); - - gui.set_deposit("token1".to_string(), "999".to_string()) - .await - .unwrap(); - let has_any_deposit = gui.has_any_deposit().unwrap(); - assert!(has_any_deposit); - } - - #[wasm_bindgen_test] - async fn test_check_deposits() { - let mut gui = initialize_gui(None).await; - - gui.set_deposit("token1".to_string(), "999".to_string()) - .await - .unwrap(); - gui.check_deposits().unwrap(); - gui.unset_deposit("token1".to_string()).unwrap(); - - let err = gui.check_deposits().unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::DepositNotSet("T1".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "A deposit for token 'T1' is required but has not been set." - ); - - let gui = initialize_gui_with_select_tokens().await; - let err = gui.check_deposits().unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::MissingDepositToken("select-token-deployment".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "A deposit for token is required but has not been set for deployment 'select-token-deployment'." - ); - } - - #[wasm_bindgen_test] - async fn test_save_deposit_minimum_validation() { - let mut gui = initialize_validation_gui().await; - let result = gui - .set_deposit("token1".to_string(), "50".to_string()) - .await; - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::BelowMinimum { - name, - value, - minimum, - })) => { - assert_eq!(name, "Token 1"); - assert_eq!(value, "50"); - assert_eq!(minimum, "100"); - } - _ => panic!("Expected BelowMinimum error"), - } - let result = gui - .set_deposit("token1".to_string(), "100".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token1".to_string(), "500".to_string()) - .await; - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_save_deposit_maximum_validation() { - let mut gui = initialize_validation_gui().await; - let result = gui - .set_deposit("token2".to_string(), "5000".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token2".to_string(), "10000".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token2".to_string(), "15000".to_string()) - .await; - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::AboveMaximum { - name, - value, - maximum, - })) => { - assert_eq!(name, "Token 2"); - assert_eq!(value, "15000"); - assert_eq!(maximum, "10000"); - } - _ => panic!("Expected AboveMaximum error"), - } - } - - #[wasm_bindgen_test] - async fn test_save_deposit_exclusive_bounds() { - let mut gui = initialize_validation_gui().await; - let result = gui.set_deposit("token3".to_string(), "0".to_string()).await; - match result { - Err(GuiError::ValidationError( - validation::GuiValidationError::BelowExclusiveMinimum { - name, - value, - exclusive_minimum, - }, - )) => { - assert_eq!(name, "Token 3"); - assert_eq!(value, "0"); - assert_eq!(exclusive_minimum, "0"); - } - _ => panic!("Expected BelowExclusiveMinimum error"), - } - let result = gui - .set_deposit("token3".to_string(), "0.001".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token3".to_string(), "49999.999".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token3".to_string(), "50000".to_string()) - .await; - match result { - Err(GuiError::ValidationError( - validation::GuiValidationError::AboveExclusiveMaximum { - name, - value, - exclusive_maximum, - }, - )) => { - assert_eq!(name, "Token 3"); - assert_eq!(value, "50000"); - assert_eq!(exclusive_maximum, "50000"); - } - _ => panic!("Expected AboveExclusiveMaximum error"), - } - } - - #[wasm_bindgen_test] - async fn test_save_deposit_multiple_constraints() { - let mut gui = initialize_validation_gui().await; - - let result = gui - .set_deposit("token4".to_string(), "50".to_string()) - .await; - assert!(result.is_ok()); - - let result = gui.set_deposit("token4".to_string(), "5".to_string()).await; - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::BelowMinimum { .. } - )) - )); - - let result = gui - .set_deposit("token4".to_string(), "1005".to_string()) - .await; - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::AboveMaximum { .. } - )) - )); - - let result = gui - .set_deposit("token4".to_string(), "10".to_string()) - .await; - assert!(result.is_ok()); - - let result = gui - .set_deposit("token4".to_string(), "1000".to_string()) - .await; - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_save_deposit_no_validation() { - let mut gui = initialize_validation_gui().await; - let result = gui.set_deposit("token6".to_string(), "0".to_string()).await; - assert!(result.is_ok()); - - let result = gui - .set_deposit("token6".to_string(), "123456789.123456789".to_string()) - .await; - assert!(result.is_ok()); - - let result = gui - .set_deposit("token6".to_string(), "0.00000001".to_string()) - .await; - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_save_deposit_invalid_formats() { - let mut gui = initialize_validation_gui().await; - - let result = gui - .set_deposit("token1".to_string(), "abc".to_string()) - .await; - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::FloatError(..) - )) - )); - - let result = gui - .set_deposit("token1".to_string(), "12.34.56".to_string()) - .await; - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::FloatError(..) - )) - )); - - let result = gui - .set_deposit("token1".to_string(), "12,345".to_string()) - .await; - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::FloatError(..) - )) - )); - } - - #[wasm_bindgen_test] - async fn test_save_deposit_edge_cases() { - let mut gui = initialize_validation_gui().await; - let result = gui - .set_deposit("token3".to_string(), "0.001".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token2".to_string(), "9999.999999999".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token1".to_string(), "100.00000".to_string()) - .await; - assert!(result.is_ok()); - let result = gui - .set_deposit("token1".to_string(), "00100".to_string()) - .await; - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_save_deposit_with_presets_and_validation() { - let mut gui = initialize_validation_gui().await; - let result = gui - .set_deposit("token1".to_string(), "200".to_string()) - .await; - assert!(result.is_ok()); - let deposits = gui.get_deposits().unwrap(); - assert_eq!(deposits.len(), 1); - assert_eq!(deposits[0].token, "token1"); - assert_eq!(deposits[0].amount, "200"); - } -} diff --git a/crates/js_api/src/gui/field_values.rs b/crates/js_api/src/gui/field_values.rs deleted file mode 100644 index 9af30f8a08..0000000000 --- a/crates/js_api/src/gui/field_values.rs +++ /dev/null @@ -1,1008 +0,0 @@ -use super::*; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] - -pub struct FieldValuePair { - field: String, - value: String, -} -impl_wasm_traits!(FieldValuePair); - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -#[serde(rename_all = "camelCase")] -pub struct PairValue { - pub is_preset: bool, - pub value: String, -} -impl_wasm_traits!(PairValue); - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] -pub struct FieldValue { - pub field: String, - pub value: String, - pub is_preset: bool, -} -impl_wasm_traits!(FieldValue); - -impl DotrainOrderGui { - pub fn check_field_values(&mut self) -> Result<(), GuiError> { - let deployment = self.get_current_deployment()?; - - for field in deployment.fields.iter() { - if self.field_values.contains_key(&field.binding) { - continue; - } - - match &field.default { - Some(default_value) => { - self.set_field_value(field.binding.clone(), default_value.clone())?; - } - None => return Err(GuiError::FieldValueNotSet(field.name.clone())), - } - } - Ok(()) - } -} - -#[wasm_export] -impl DotrainOrderGui { - /// Sets a value for a specific field binding with automatic preset detection. - /// - /// This function stores the provided value and automatically determines if it matches - /// a preset from the field definition. Preset detection enables the UI to show - /// whether a standard option or custom value is being used. - /// - /// ## Preset Detection - /// - /// The function checks if the value matches any preset in the field definition. - /// If it matches, it stores the preset index; otherwise, stores the raw value. - /// - /// ## Examples - /// - /// ```javascript - /// // Set a custom value - /// const result = gui.setFieldValue("max-price", "1500.50"); - /// if (result.error) { - /// console.error("Set failed:", result.error.readableMsg); - /// return; - /// } - /// ``` - #[wasm_export(js_name = "setFieldValue", unchecked_return_type = "void")] - pub fn set_field_value( - &mut self, - #[wasm_export(param_description = "Field identifier from the YAML configuration")] - field: String, - #[wasm_export(param_description = "Value to save (can be a preset value or custom input)")] - value: String, - ) -> Result<(), GuiError> { - let field_definition = self.get_field_definition(&field)?; - - if let Some(validation) = &field_definition.validation { - validation::validate_field_value(&field_definition.name, &value, validation)?; - } - - let value = match field_definition.presets.as_ref() { - Some(presets) => match presets.iter().position(|p| p.value == value) { - Some(index) => field_values::PairValue { - is_preset: true, - value: index.to_string(), - }, - None => field_values::PairValue { - is_preset: false, - value, - }, - }, - None => field_values::PairValue { - is_preset: false, - value, - }, - }; - - self.field_values.insert(field, value); - - self.execute_state_update_callback()?; - Ok(()) - } - - /// Batch sets multiple field values in a single operation. - /// - /// This is more efficient than calling setFieldValue multiple times, especially - /// when loading saved configurations or applying templates. Each value is processed - /// with the same preset detection as individual saves. - /// - /// ## Examples - /// - /// ```javascript - /// const fields = [ - /// { field: "max-price", value: "1500" }, - /// { field: "min-amount", value: "100" }, - /// { field: "slippage", value: "0.5" } - /// ]; - /// - /// const result = gui.setFieldValues(fields); - /// if (result.error) { - /// console.error("Batch set failed:", result.error.readableMsg); - /// return; - /// } - /// ``` - #[wasm_export(js_name = "setFieldValues", unchecked_return_type = "void")] - pub fn set_field_values( - &mut self, - #[wasm_export(param_description = "Array of field-value pairs to save")] field_values: Vec< - FieldValuePair, - >, - ) -> Result<(), GuiError> { - for field_value in field_values { - self.set_field_value(field_value.field, field_value.value)?; - } - Ok(()) - } - - /// Unsets a previously set field value. - /// - /// Use this to clear a field value, returning it to an unset state. - /// - /// ## Examples - /// - /// ```javascript - /// // Clear a field value - /// const result = gui.unsetFieldValue("max-price"); - /// if (result.error) { - /// console.error("Unset failed:", result.error.readableMsg); - /// return; - /// } - /// ``` - #[wasm_export(js_name = "unsetFieldValue", unchecked_return_type = "void")] - pub fn unset_field_value( - &mut self, - #[wasm_export(param_description = "Field identifier from the YAML configuration")] - field: String, - ) -> Result<(), GuiError> { - self.field_values.remove(&field); - self.execute_state_update_callback()?; - Ok(()) - } - - /// Retrieves a field value with preset expansion and metadata. - /// - /// This function returns the saved value along with information about whether it's a preset. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getFieldValue("max-price"); - /// if (result.error) { - /// console.error("Not found:", result.error.readableMsg); - /// return; - /// } - /// - /// const { field, value, isPreset } = result.value; - /// // field is the field identifier - /// // value is the field value - /// // isPreset is a boolean indicating if the value is a preset - /// ``` - #[wasm_export( - js_name = "getFieldValue", - unchecked_return_type = "FieldValue", - return_description = "Field value with the identifier, value, and preset flag" - )] - pub fn get_field_value( - &self, - #[wasm_export(param_description = "Field identifier from the YAML configuration")] - field: String, - ) -> Result { - let field_value = self - .field_values - .get(&field) - .ok_or(GuiError::FieldBindingNotFound(field.clone()))?; - let preset = match field_value.is_preset { - true => { - let field_definition = self.get_field_definition(&field)?; - let presets = field_definition - .presets - .ok_or(GuiError::BindingHasNoPresets(field.clone()))?; - presets - .iter() - .find(|preset| preset.id == field_value.value) - .ok_or(GuiError::InvalidPreset)? - .clone() - } - false => GuiPresetCfg { - id: "".to_string(), - name: None, - value: field_value.value.clone(), - }, - }; - Ok(FieldValue { - field: field.clone(), - value: preset.value.clone(), - is_preset: field_value.is_preset, - }) - } - - /// Gets all configured field values with their metadata. - /// - /// Returns all field values that have been set, with preset values expanded. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getAllFieldValues(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// const [fieldValue1, fieldValue2, ...] = result.value; - /// const { - /// // field is the field identifier - /// field, - /// // value is the field value - /// value, - /// // isPreset is a boolean indicating if the value is a preset - /// isPreset - /// } = fieldValue1; - /// ``` - #[wasm_export( - js_name = "getAllFieldValues", - unchecked_return_type = "FieldValue[]", - return_description = "Array of all configured field values" - )] - pub fn get_all_field_values(&self) -> Result, GuiError> { - let mut result = Vec::new(); - for (binding, _) in self.field_values.iter() { - let field_value = self.get_field_value(binding.clone())?; - result.push(field_value); - } - Ok(result) - } - - /// Gets the complete definition for a specific field including presets and validation. - /// - /// Use this to build dynamic UIs that adapt to field configurations, showing - /// appropriate input controls, preset options, and validation rules. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getFieldDefinition("max-price"); - /// if (result.error) { - /// console.error("Not found:", result.error.readableMsg); - /// return; - /// } - /// - /// const { - /// // field is the field identifier - /// field, - /// // name is the display name for the field - /// name, - /// // description is the help text for the field - /// description, - /// // presets are the available preset options for the field - /// presets, - /// // default is the default value for the field if no value is set by the user - /// default, - /// // showCustomField is a boolean indicating if the field allows custom input - /// showCustomField - /// } = result.value; - /// ``` - #[wasm_export( - js_name = "getFieldDefinition", - unchecked_return_type = "GuiFieldDefinitionCfg", - return_description = "Complete field configuration" - )] - pub fn get_field_definition( - &self, - #[wasm_export(param_description = "Field binding identifier to look up")] field: &str, - ) -> Result { - let deployment = self.get_current_deployment()?; - let field_definition = deployment - .fields - .iter() - .find(|field_definition| field_definition.binding == field) - .ok_or(GuiError::FieldBindingNotFound(field.to_string()))?; - Ok(field_definition.clone()) - } - - /// Gets all field definitions with optional filtering by default value presence. - /// - /// This method helps build dynamic forms by providing all field configurations - /// at once. The filter option allows separating required fields (no default) - /// from optional fields (with default). - /// - /// ## Examples - /// - /// ```javascript - /// // Get all fields - /// const allFieldsResult = gui.getAllFieldDefinitions(); - /// if (allFieldsResult.error) { - /// console.error("Error:", allFieldsResult.error.readableMsg); - /// return; - /// } - /// - /// // Get only required fields (no defaults) - /// const requiredResult = gui.getAllFieldDefinitions(false); - /// if (requiredResult.error) { - /// console.error("Error:", requiredResult.error.readableMsg); - /// return; - /// } - /// const requiredFields = requiredResult.value; - /// - /// // Get optional fields (with defaults) - /// const optionalResult = gui.getAllFieldDefinitions(true); - /// if (optionalResult.error) { - /// console.error("Error:", optionalResult.error.readableMsg); - /// return; - /// } - /// const optionalFields = optionalResult.value; - /// - /// ``` - #[wasm_export( - js_name = "getAllFieldDefinitions", - unchecked_return_type = "GuiFieldDefinitionCfg[]", - return_description = "Filtered field definitions" - )] - pub fn get_all_field_definitions( - &self, - #[wasm_export( - param_description = "Optional filter: **true** for fields with defaults, **false** for fields without defaults, **undefined** for all" - )] - filter_defaults: Option, - ) -> Result, GuiError> { - let deployment = self.get_current_deployment()?; - let mut field_definitions = deployment.fields.clone(); - - match filter_defaults { - Some(true) => field_definitions.retain(|field| field.default.is_some()), - Some(false) => field_definitions.retain(|field| field.default.is_none()), - None => (), - } - - field_definitions.sort_by(|a, b| a.binding.cmp(&b.binding)); - - Ok(field_definitions) - } - - /// Lists field definitions that haven't been configured yet. - /// - /// Returns field definitions for fields that need values. - /// Use this for validation and to guide users through required configurations. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getMissingFieldValues(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// const missing = result.value; - /// // Do something with the missing - /// ``` - #[wasm_export( - js_name = "getMissingFieldValues", - unchecked_return_type = "GuiFieldDefinitionCfg[]", - return_description = "Array of field definitions that need to be set" - )] - pub fn get_missing_field_values(&self) -> Result, GuiError> { - let deployment = self.get_current_deployment()?; - let mut missing_field_values = Vec::new(); - - for field in deployment.fields.iter() { - if !self.field_values.contains_key(&field.binding) { - missing_field_values.push(field.clone()); - } - } - Ok(missing_field_values) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::gui::tests::{initialize_gui, initialize_validation_gui}; - use crate::gui::validation; - use wasm_bindgen_test::wasm_bindgen_test; - - #[wasm_bindgen_test] - async fn test_set_get_field_value() { - let mut gui = initialize_gui(None).await; - - gui.set_field_value("binding-1".to_string(), "some-default-value".to_string()) - .unwrap(); - gui.set_field_value("binding-2".to_string(), "99.2".to_string()) - .unwrap(); - - let field_value = gui.get_field_value("binding-1".to_string()).unwrap(); - assert_eq!(field_value.field, "binding-1"); - assert_eq!(field_value.value, "some-default-value"); - assert!(!field_value.is_preset); - - let field_value = gui.get_field_value("binding-2".to_string()).unwrap(); - assert_eq!(field_value.field, "binding-2"); - assert_eq!(field_value.value, "99.2"); - assert!(field_value.is_preset); - } - - #[wasm_bindgen_test] - async fn test_set_get_all_field_values() { - let mut gui = initialize_gui(None).await; - - gui.set_field_values(vec![ - FieldValuePair { - field: "binding-1".to_string(), - value: "some-default-value".to_string(), - }, - FieldValuePair { - field: "binding-2".to_string(), - value: "99.2".to_string(), - }, - ]) - .unwrap(); - - let field_values = gui.get_all_field_values().unwrap(); - assert_eq!(field_values.len(), 2); - assert_eq!(field_values[0].field, "binding-1"); - assert_eq!(field_values[0].value, "some-default-value"); - assert!(!field_values[0].is_preset); - assert_eq!(field_values[1].field, "binding-2"); - assert_eq!(field_values[1].value, "99.2"); - assert!(field_values[1].is_preset); - } - - fn get_binding_1() -> GuiFieldDefinitionCfg { - GuiFieldDefinitionCfg { - binding: String::from("binding-1"), - name: String::from("Field 1 name"), - description: Some(String::from("Field 1 description")), - presets: Some(Vec::from([ - GuiPresetCfg { - id: String::from("0"), - name: Some(String::from("Preset 1")), - value: String::from("0x1234567890abcdef1234567890abcdef12345678"), - }, - GuiPresetCfg { - id: String::from("1"), - name: Some(String::from("Preset 2")), - value: String::from("false"), - }, - GuiPresetCfg { - id: String::from("2"), - name: Some(String::from("Preset 3")), - value: String::from("some-string"), - }, - ])), - default: Some(String::from("some-default-value")), - show_custom_field: None, - validation: None, - } - } - - fn get_binding_2() -> GuiFieldDefinitionCfg { - GuiFieldDefinitionCfg { - binding: String::from("binding-2"), - name: String::from("Field 2 name"), - description: Some(String::from("Field 2 description")), - presets: Some(Vec::from([ - GuiPresetCfg { - id: String::from("0"), - name: None, - value: String::from("99.2"), - }, - GuiPresetCfg { - id: String::from("1"), - name: None, - value: String::from("582.1"), - }, - GuiPresetCfg { - id: String::from("2"), - name: None, - value: String::from("648.239"), - }, - ])), - default: None, - show_custom_field: Some(true), - validation: None, - } - } - - #[wasm_bindgen_test] - async fn test_unset_field_value() { - let mut gui = initialize_gui(None).await; - - gui.set_field_value("binding-1".to_string(), "some-default-value".to_string()) - .unwrap(); - gui.get_field_value("binding-1".to_string()).unwrap(); - - gui.unset_field_value("binding-1".to_string()).unwrap(); - let err = gui.get_field_value("binding-1".to_string()).unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::FieldBindingNotFound("binding-1".to_string()).to_string() - ); - } - - #[wasm_bindgen_test] - async fn test_get_field_definition() { - let gui = initialize_gui(None).await; - - let field_definition = gui.get_field_definition("binding-1").unwrap(); - assert_eq!(field_definition, get_binding_1()); - - let field_definition = gui.get_field_definition("binding-2").unwrap(); - assert_eq!(field_definition, get_binding_2()); - - let err = gui.get_field_definition("invalid-binding").unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::FieldBindingNotFound("invalid-binding".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The field binding 'invalid-binding' could not be found in the YAML configuration." - ); - } - - #[wasm_bindgen_test] - async fn test_get_all_field_definitions() { - let gui = initialize_gui(None).await; - - let field_definitions = gui.get_all_field_definitions(None).unwrap(); - assert_eq!(field_definitions.len(), 2); - assert_eq!(field_definitions[0], get_binding_1()); - assert_eq!(field_definitions[1], get_binding_2()); - - let field_definitions = gui.get_all_field_definitions(Some(true)).unwrap(); - assert_eq!(field_definitions.len(), 1); - assert_eq!(field_definitions[0], get_binding_1()); - - let field_definitions = gui.get_all_field_definitions(Some(false)).unwrap(); - assert_eq!(field_definitions.len(), 1); - assert_eq!(field_definitions[0], get_binding_2()); - } - - #[wasm_bindgen_test] - async fn test_get_missing_field_values() { - let mut gui = initialize_gui(None).await; - - let field_values = gui.get_missing_field_values().unwrap(); - assert_eq!(field_values.len(), 2); - assert_eq!(field_values[0], get_binding_1()); - assert_eq!(field_values[1], get_binding_2()); - - gui.set_field_value("binding-1".to_string(), "some-default-value".to_string()) - .unwrap(); - - let field_values = gui.get_missing_field_values().unwrap(); - assert_eq!(field_values.len(), 1); - assert_eq!(field_values[0], get_binding_2()); - } - - #[wasm_bindgen_test] - async fn test_check_field_values() { - let mut gui = initialize_gui(None).await; - - let err = gui.check_field_values().unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::FieldValueNotSet("Field 2 name".to_string()).to_string() - ); - - gui.set_field_value("binding-2".to_string(), "99.2".to_string()) - .unwrap(); - let res = gui.check_field_values(); - assert!(res.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_number_minimum_maximum() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("price-field".to_string(), "50.00".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("price-field".to_string(), "5.00".to_string()); - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::BelowMinimum { - name, - value, - minimum, - })) => { - assert_eq!(name, "Price Field"); - assert_eq!(value, "5.00"); - assert_eq!(minimum, "10"); - } - _ => panic!("Expected BelowMinimum error"), - } - - let result = gui.set_field_value("price-field".to_string(), "1500.00".to_string()); - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::AboveMaximum { - name, - value, - maximum, - })) => { - assert_eq!(name, "Price Field"); - assert_eq!(value, "1500.00"); - assert_eq!(maximum, "1000"); - } - _ => panic!("Expected AboveMaximum error"), - } - - let result = gui.set_field_value("price-field".to_string(), "10".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("price-field".to_string(), "1000".to_string()); - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_number_exclusive_bounds() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("quantity-field".to_string(), "0".to_string()); - match result { - Err(GuiError::ValidationError( - validation::GuiValidationError::BelowExclusiveMinimum { - name, - value, - exclusive_minimum, - }, - )) => { - assert_eq!(name, "Quantity Field"); - assert_eq!(value, "0"); - assert_eq!(exclusive_minimum, "0"); - } - _ => panic!("Expected BelowExclusiveMinimum error"), - } - - let result = gui.set_field_value("quantity-field".to_string(), "0.001".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("quantity-field".to_string(), "100000".to_string()); - match result { - Err(GuiError::ValidationError( - validation::GuiValidationError::AboveExclusiveMaximum { - name, - value, - exclusive_maximum, - }, - )) => { - assert_eq!(name, "Quantity Field"); - assert_eq!(value, "100000"); - assert_eq!(exclusive_maximum, "100000"); - } - _ => panic!("Expected AboveExclusiveMaximum error"), - } - - let result = gui.set_field_value("quantity-field".to_string(), "99999.999".to_string()); - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_number_complex_constraints() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("percentage-field".to_string(), "50.5".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("percentage-field".to_string(), "0".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("percentage-field".to_string(), "100".to_string()); - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_number_invalid_formats() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("simple-number".to_string(), "".to_string()); - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::InvalidNumber { - name, - value, - })) => { - assert_eq!(name, "Simple Number"); - assert_eq!(value, ""); - } - _ => panic!("Expected InvalidNumber error"), - } - - let result = gui.set_field_value("simple-number".to_string(), "abc".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::FloatError(..) - )) - )); - - let result = gui.set_field_value("simple-number".to_string(), "12.34.56".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::FloatError(..) - )) - )); - - let result = gui.set_field_value("simple-number".to_string(), "١٢٣".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::FloatError(..) - )) - )); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_string_length_constraints() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("username-field".to_string(), "john_doe".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("username-field".to_string(), "jo".to_string()); - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::StringTooShort { - name, - length, - minimum, - })) => { - assert_eq!(name, "Username"); - assert_eq!(length, 2); - assert_eq!(minimum, 3); - } - _ => panic!("Expected StringTooShort error"), - } - - let result = gui.set_field_value( - "username-field".to_string(), - "this_username_is_way_too_long".to_string(), - ); - match result { - Err(GuiError::ValidationError(validation::GuiValidationError::StringTooLong { - name, - length, - maximum, - })) => { - assert_eq!(name, "Username"); - assert_eq!(length, 29); - assert_eq!(maximum, 20); - } - _ => panic!("Expected StringTooLong error"), - } - - let result = gui.set_field_value("username-field".to_string(), "abc".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("username-field".to_string(), "a".repeat(20)); - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_string_edge_cases() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("description-field".to_string(), "".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("code-field".to_string(), "".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::StringTooShort { .. } - )) - )); - - let result = gui.set_field_value("description-field".to_string(), "a".repeat(500)); - assert!(result.is_ok()); - - let result = gui.set_field_value("description-field".to_string(), "a".repeat(501)); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::StringTooLong { .. } - )) - )); - - let result = - gui.set_field_value("username-field".to_string(), "🦀🦀🦀rust🦀🦀🦀".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::StringTooLong { .. } - )) - )); - - let result = gui.set_field_value( - "any-string".to_string(), - "Any value at all!@#$%^&*()".to_string(), - ); - assert!(result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_boolean() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("enabled-field".to_string(), "true".to_string()); - assert!(result.is_ok()); - - let result = gui.set_field_value("enabled-field".to_string(), "false".to_string()); - assert!(result.is_ok()); - - let test_cases = vec![ - "True", "FALSE", "yes", "no", "on", "off", "", " true", "true ", "1", "0", - ]; - - for test_value in test_cases { - let result = gui.set_field_value("enabled-field".to_string(), test_value.to_string()); - match result { - Err(GuiError::ValidationError( - validation::GuiValidationError::InvalidBoolean { name, value }, - )) => { - assert_eq!(name, "Enabled"); - assert_eq!(value, test_value); - } - _ => panic!("Expected InvalidBoolean error for value: {}", test_value), - } - } - } - - #[wasm_bindgen_test] - async fn test_set_field_value_preset_with_validation() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("preset-number-field".to_string(), "100".to_string()); - assert!(result.is_ok()); - let field_value = gui - .get_field_value("preset-number-field".to_string()) - .unwrap(); - assert!(field_value.is_preset); - assert_eq!(field_value.value, "100"); - - let result = gui.set_field_value("preset-number-field".to_string(), "120".to_string()); - assert!(result.is_ok()); - let field_value = gui - .get_field_value("preset-number-field".to_string()) - .unwrap(); - assert!(!field_value.is_preset); - assert_eq!(field_value.value, "120"); - - let result = gui.set_field_value("preset-number-field".to_string(), "5".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::BelowMinimum { .. } - )) - )); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_string_preset_with_validation() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value("preset-string-field".to_string(), "alpha".to_string()); - assert!(result.is_ok()); - let field_value = gui - .get_field_value("preset-string-field".to_string()) - .unwrap(); - assert!(field_value.is_preset); - - let result = gui.set_field_value("preset-string-field".to_string(), "custom".to_string()); - assert!(result.is_ok()); - let field_value = gui - .get_field_value("preset-string-field".to_string()) - .unwrap(); - assert!(!field_value.is_preset); - - let result = gui.set_field_value("preset-string-field".to_string(), "xyz".to_string()); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::StringTooShort { .. } - )) - )); - - let result = gui.set_field_value( - "preset-string-field".to_string(), - "verylongvalue".to_string(), - ); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::StringTooLong { .. } - )) - )); - } - - #[wasm_bindgen_test] - async fn test_set_field_value_no_validation() { - let mut gui = initialize_validation_gui().await; - - let test_values = vec![ - "123", - "abc", - "true", - "false", - "", - "!@#$%^&*()", - "very long string with many characters", - "0", - "🦀 Rust 🦀", - ]; - - for value in test_values { - let result = gui.set_field_value("no-validation-field".to_string(), value.to_string()); - assert!(result.is_ok(), "Failed to save value: {}", value); - - let field_value = gui - .get_field_value("no-validation-field".to_string()) - .unwrap(); - assert_eq!(field_value.value, value); - } - } - - #[wasm_bindgen_test] - async fn test_set_field_values_batch_with_validation() { - let mut gui = initialize_validation_gui().await; - - let valid_batch = vec![ - FieldValuePair { - field: "price-field".to_string(), - value: "100.00".to_string(), - }, - FieldValuePair { - field: "username-field".to_string(), - value: "valid_user".to_string(), - }, - FieldValuePair { - field: "enabled-field".to_string(), - value: "true".to_string(), - }, - ]; - - let result = gui.set_field_values(valid_batch); - assert!(result.is_ok()); - - let invalid_batch = vec![ - FieldValuePair { - field: "price-field".to_string(), - value: "100.00".to_string(), - }, - FieldValuePair { - field: "username-field".to_string(), - value: "ab".to_string(), // Too short - }, - FieldValuePair { - field: "enabled-field".to_string(), - value: "true".to_string(), - }, - ]; - - let result = gui.set_field_values(invalid_batch); - assert!(matches!( - result, - Err(GuiError::ValidationError( - validation::GuiValidationError::StringTooShort { .. } - )) - )); - - let field_result = gui.get_field_value("price-field".to_string()); - assert!(field_result.is_ok()); - } - - #[wasm_bindgen_test] - async fn test_very_precise_decimal_validation() { - let mut gui = initialize_validation_gui().await; - - let result = gui.set_field_value( - "simple-number".to_string(), - "0.123456789012345678".to_string(), - ); - assert!(result.is_ok()); - - let result = gui.set_field_value("price-field".to_string(), "999.99".to_string()); - assert!(result.is_ok()); - } -} diff --git a/crates/js_api/src/gui/select_tokens.rs b/crates/js_api/src/gui/select_tokens.rs deleted file mode 100644 index 705be8e644..0000000000 --- a/crates/js_api/src/gui/select_tokens.rs +++ /dev/null @@ -1,1035 +0,0 @@ -use super::*; -use futures::StreamExt; -use rain_math_float::Float; -use rain_orderbook_app_settings::{ - deployment::DeploymentCfg, gui::GuiSelectTokensCfg, network::NetworkCfg, order::OrderCfg, - token::TokenCfg, yaml::YamlParsableHash, -}; -use rain_orderbook_common::raindex_client::vaults::AccountBalance; -use std::str::FromStr; - -const MAX_CONCURRENT_FETCHES: usize = 5; - -#[wasm_export] -impl DotrainOrderGui { - /// Gets tokens that need user selection for the current deployment. - /// - /// Returns tokens defined in the select-tokens section that require user input - /// to specify contract addresses. This enables generic orders that work - /// with user-chosen tokens. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.getSelectTokens(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// const selectableTokens = result.value; - /// // Do something with the selectable tokens - /// ``` - #[wasm_export( - js_name = "getSelectTokens", - unchecked_return_type = "GuiSelectTokensCfg[]", - return_description = "Array of selectable token configurations" - )] - pub fn get_select_tokens(&self) -> Result, GuiError> { - let select_tokens = GuiCfg::parse_select_tokens( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )?; - Ok(select_tokens.unwrap_or(vec![])) - } - - /// Checks if a selectable token has been configured with an address. - /// - /// Use this to determine if a user has provided an address for a select-token, - /// enabling progressive UI updates and validation. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.isSelectTokenSet("stable-token"); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// const isSelectTokenSet = result.value; - /// // Do something - /// ``` - #[wasm_export( - js_name = "isSelectTokenSet", - unchecked_return_type = "boolean", - return_description = "True if token address has been set" - )] - pub fn is_select_token_set( - &self, - #[wasm_export(param_description = "Token key from select-tokens configuration")] - key: String, - ) -> Result { - Ok(self.dotrain_order.orderbook_yaml().get_token(&key).is_ok()) - } - - /// Validates that all required tokens have been selected. - /// - /// Checks if all tokens in the select-tokens configuration have been given - /// addresses. Use this before generating transactions to ensure completeness. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.checkSelectTokens(); - /// if (result.error) { - /// console.error("Selection incomplete:", result.error.readableMsg); - /// return; - /// // Do something - /// ``` - #[wasm_export( - js_name = "checkSelectTokens", - unchecked_return_type = "void", - return_description = "All required tokens are configured" - )] - pub fn check_select_tokens(&self) -> Result<(), GuiError> { - let select_tokens = GuiCfg::parse_select_tokens( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )?; - - if let Some(select_tokens) = select_tokens { - for select_token in select_tokens { - if self - .dotrain_order - .orderbook_yaml() - .get_token(&select_token.key) - .is_err() - { - return Err(GuiError::TokenMustBeSelected(select_token.key.clone())); - } - } - } - - Ok(()) - } - - /// Sets a custom token address to be used in the order. - /// - /// Takes a token address provided by the user and queries the blockchain to get - /// the token's name, symbol, and decimals. This information is then cached for efficient access. - /// - /// # Network Usage - /// - /// The function uses the deployment's network configuration to determine the - /// RPC endpoint for querying token information. - /// - /// ## Examples - /// - /// ```javascript - /// // User selects token - /// const result = await gui.setSelectToken( - /// "stable-token", - /// "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" - /// ); - /// - /// if (result.error) { - /// console.error("Selection failed:", result.error.readableMsg); - /// return; - /// } - /// // Do something with the token - /// ``` - #[wasm_export(js_name = "setSelectToken", unchecked_return_type = "void")] - pub async fn set_select_token( - &mut self, - #[wasm_export(param_description = "Token key from select-tokens configuration")] - key: String, - #[wasm_export(param_description = "Token contract address provided by user")] - address: String, - ) -> Result<(), GuiError> { - let select_tokens = GuiCfg::parse_select_tokens( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )? - .ok_or(GuiError::SelectTokensNotSet)?; - if !select_tokens.iter().any(|token| token.key == key) { - return Err(GuiError::TokenNotFound(key.clone())); - } - - if TokenCfg::parse_from_yaml(self.dotrain_order.dotrain_yaml().documents, &key, None) - .is_ok() - { - TokenCfg::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key)?; - } - - let address = Address::from_str(&address)?; - - let order_key = DeploymentCfg::parse_order_key( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )?; - let network_key = - OrderCfg::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?; - let rpcs = - NetworkCfg::parse_rpcs(self.dotrain_order.dotrain_yaml().documents, &network_key)?; - - let erc20 = ERC20::new(rpcs, address); - let token_info = erc20.token_info(None).await?; - - TokenCfg::add_record_to_yaml( - self.dotrain_order.orderbook_yaml().documents, - &key, - &network_key, - &address.to_string(), - Some(&token_info.decimals.to_string()), - Some(&token_info.name), - Some(&token_info.symbol), - )?; - - self.execute_state_update_callback()?; - Ok(()) - } - - /// Removes a previously selected token configuration. - /// - /// Clears the address and cached information for a select-token, returning it - /// to an unselected state. - /// - /// ## Examples - /// - /// ```javascript - /// // Remove token selection - /// const result = gui.unsetSelectToken("stable-token"); - /// if (result.error) { - /// console.error("Remove failed:", result.error.readableMsg); - /// return; - /// } - /// ``` - #[wasm_export(js_name = "unsetSelectToken", unchecked_return_type = "void")] - pub fn unset_select_token( - &mut self, - #[wasm_export(param_description = "Token key to clear")] key: String, - ) -> Result<(), GuiError> { - let select_tokens = GuiCfg::parse_select_tokens( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )? - .ok_or(GuiError::SelectTokensNotSet)?; - if !select_tokens.iter().any(|token| token.key == key) { - return Err(GuiError::TokenNotFound(key.clone())); - } - - TokenCfg::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key)?; - - self.execute_state_update_callback()?; - Ok(()) - } - - /// Checks if all required tokens have been selected and configured. - /// - /// Validates that every token in the select-tokens configuration has been - /// given an address. Use this for overall validation and progress tracking. - /// - /// ## Examples - /// - /// ```javascript - /// const result = gui.areAllTokensSelected(); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// const allSelected = result.value; - /// // Do something - /// ``` - #[wasm_export( - js_name = "areAllTokensSelected", - unchecked_return_type = "boolean", - return_description = "True if all tokens are configured" - )] - pub fn are_all_tokens_selected(&self) -> Result { - let select_tokens = self.get_select_tokens()?; - for token in select_tokens { - if !self.is_select_token_set(token.key)? { - return Ok(false); - } - } - Ok(true) - } - - /// Gets all tokens configured for the selected deployment's network. - /// - /// Retrieves token information from the YAML configuration, using cached - /// metadata when available or fetching from blockchain via ERC20 contracts. - /// Results are filtered by the search term (matching name or address) and - /// sorted by address and deduplicated. - /// - /// ## Examples - /// - /// ```javascript - /// // Get all tokens - /// const result = await gui.getAllTokens(); - /// - /// // Search for specific tokens - /// const usdcResult = await gui.getAllTokens("USDC"); - /// const addressResult = await gui.getAllTokens("0x1234..."); - /// ``` - #[wasm_export( - js_name = "getAllTokens", - unchecked_return_type = "ExtendedTokenInfo[]", - return_description = "Array of token information for the current network" - )] - pub async fn get_all_tokens( - &self, - #[wasm_export( - param_description = "Optional search term to filter tokens by name, symbol, or address" - )] - search: Option, - ) -> Result, GuiError> { - let order_key = DeploymentCfg::parse_order_key( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )?; - let network_key = - OrderCfg::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?; - let tokens = self.dotrain_order.orderbook_yaml().get_tokens()?; - - let mut fetch_futures = Vec::new(); - - for (_, token) in tokens - .into_iter() - .filter(|(_, token)| token.network.key == network_key) - { - fetch_futures.push(async move { ExtendedTokenInfo::from_token_cfg(&token).await }); - } - - let mut results: Vec = futures::stream::iter(fetch_futures) - .buffer_unordered(MAX_CONCURRENT_FETCHES) - .filter_map(|res| async { res.ok() }) - .collect() - .await; - results.sort_by(|a, b| { - a.address - .to_string() - .to_lowercase() - .cmp(&b.address.to_string().to_lowercase()) - }); - results.dedup_by(|a, b| { - a.address.to_string().to_lowercase() == b.address.to_string().to_lowercase() - }); - - if let Some(search_term) = search { - if !search_term.is_empty() { - let search_lower = search_term.to_lowercase(); - results.retain(|token| { - token.name.to_lowercase().contains(&search_lower) - || token.symbol.to_lowercase().contains(&search_lower) - || token - .address - .to_string() - .to_lowercase() - .contains(&search_lower) - }); - } - } - - Ok(results) - } - - /// Gets the balance of a specific token for a given owner address - /// - /// Retrieves the ERC20 token balance by connecting to the current deployment's network RPC - /// and querying the token contract. - /// - /// ## Examples - /// - /// ```javascript - /// const result = await gui.getAccountBalance("0x123...", "0xabc..."); - /// if (result.error) { - /// console.error("Error:", result.error.readableMsg); - /// return; - /// } - /// - /// console.log("Raw balance:", result.value.balance); - /// console.log("Formatted balance:", result.value.formattedBalance); - /// ``` - #[wasm_export( - js_name = "getAccountBalance", - unchecked_return_type = "AccountBalance", - return_description = "Owner balance in both raw and human-readable format", - preserve_js_class - )] - pub async fn get_account_balance( - &self, - #[wasm_export(js_name = "tokenAddress", param_description = "Token contract address")] - token_address: String, - #[wasm_export( - param_description = "Owner address to check balance for", - unchecked_param_type = "Hex" - )] - owner: String, - ) -> Result { - let order_key = DeploymentCfg::parse_order_key( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )?; - let network_key = - OrderCfg::parse_network_key(self.dotrain_order.dotrain_yaml().documents, &order_key)?; - let network = self - .dotrain_order - .orderbook_yaml() - .get_network(&network_key)?; - - let erc20 = ERC20::new(network.rpcs, Address::from_str(&token_address)?); - let decimals = erc20.decimals().await?; - let balance = erc20 - .get_account_balance(Address::from_str(&owner)?) - .await?; - let float_balance = Float::from_fixed_decimal(balance, decimals)?; - - Ok(AccountBalance::new(float_balance, float_balance.format()?)) - } -} - -#[cfg(test)] -impl DotrainOrderGui { - pub fn add_record_to_yaml( - &self, - key: String, - network_key: String, - address: String, - decimals: String, - label: String, - symbol: String, - ) { - TokenCfg::add_record_to_yaml( - self.dotrain_order.orderbook_yaml().documents, - &key, - &network_key, - &address, - Some(&decimals), - Some(&label), - Some(&symbol), - ) - .unwrap(); - } - - pub fn remove_record_from_yaml(&self, key: String) { - TokenCfg::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key) - .unwrap(); - } -} - -#[cfg(test)] -mod tests { - #[cfg(target_family = "wasm")] - mod wasm_tests { - use crate::gui::{ - tests::{initialize_gui, initialize_gui_with_select_tokens}, - GuiError, - }; - use wasm_bindgen_test::wasm_bindgen_test; - - #[wasm_bindgen_test] - async fn test_get_select_tokens() { - let gui = initialize_gui_with_select_tokens().await; - let select_tokens = gui.get_select_tokens().unwrap(); - assert_eq!(select_tokens.len(), 2); - assert_eq!(select_tokens[0].key, "token3"); - assert_eq!(select_tokens[1].key, "token4"); - - let gui = initialize_gui(None).await; - let select_tokens = gui.get_select_tokens().unwrap(); - assert_eq!(select_tokens.len(), 0); - } - - #[wasm_bindgen_test] - async fn test_is_select_token_set() { - let gui = initialize_gui_with_select_tokens().await; - let is_select_token_set = gui.is_select_token_set("token3".to_string()).unwrap(); - assert!(!is_select_token_set); - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - - let is_select_token_set = gui.is_select_token_set("token3".to_string()).unwrap(); - assert!(is_select_token_set); - } - - #[wasm_bindgen_test] - async fn test_check_select_tokens() { - let gui = initialize_gui_with_select_tokens().await; - - let err = gui.check_select_tokens().unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::TokenMustBeSelected("token3".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The token 'token3' must be selected to proceed." - ); - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - - let err = gui.check_select_tokens().unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::TokenMustBeSelected("token4".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The token 'token4' must be selected to proceed." - ); - - gui.add_record_to_yaml( - "token4".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "18".to_string(), - "Token 4".to_string(), - "T4".to_string(), - ); - - assert!(gui.check_select_tokens().is_ok()); - } - - #[wasm_bindgen_test] - async fn test_set_select_token() { - let mut gui = initialize_gui_with_select_tokens().await; - let err = gui - .set_select_token( - "token5".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - ) - .await - .unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::TokenNotFound("token5".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The token 'token5' could not be found in the YAML configuration." - ); - } - - #[wasm_bindgen_test] - async fn test_remove_select_token() { - let mut gui = initialize_gui_with_select_tokens().await; - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - - let err = gui.unset_select_token("token5".to_string()).unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::TokenNotFound("token5".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The token 'token5' could not be found in the YAML configuration." - ); - - assert!(gui.unset_select_token("token3".to_string()).is_ok()); - assert!(!gui.is_select_token_set("token3".to_string()).unwrap()); - - let mut gui = initialize_gui(None).await; - let err = gui.unset_select_token("token3".to_string()).unwrap_err(); - assert_eq!(err.to_string(), GuiError::SelectTokensNotSet.to_string()); - assert_eq!( - err.to_readable_msg(), - "No tokens have been configured for selection. Please check your YAML configuration." - ); - } - - #[wasm_bindgen_test] - async fn test_are_all_tokens_selected() { - let gui = initialize_gui_with_select_tokens().await; - - let are_all_tokens_selected = gui.are_all_tokens_selected().unwrap(); - assert!(!are_all_tokens_selected); - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - - let are_all_tokens_selected = gui.are_all_tokens_selected().unwrap(); - assert!(!are_all_tokens_selected); - - gui.add_record_to_yaml( - "token4".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "18".to_string(), - "Token 4".to_string(), - "T4".to_string(), - ); - - let are_all_tokens_selected = gui.are_all_tokens_selected().unwrap(); - assert!(are_all_tokens_selected); - } - - #[wasm_bindgen_test] - async fn test_get_all_tokens() { - let gui = initialize_gui_with_select_tokens().await; - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - gui.add_record_to_yaml( - "token4".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "6".to_string(), - "Token 4".to_string(), - "T4".to_string(), - ); - gui.add_record_to_yaml( - "token-other".to_string(), - "other-network".to_string(), - "0x0000000000000000000000000000000000000003".to_string(), - "8".to_string(), - "Token Other".to_string(), - "TO".to_string(), - ); - - let tokens = gui.get_all_tokens(None).await.unwrap(); - assert_eq!(tokens.len(), 4); - assert_eq!( - tokens[0].address.to_string(), - "0x0000000000000000000000000000000000000001" - ); - assert_eq!(tokens[0].decimals, 18); - assert_eq!(tokens[0].name, "Token 3"); - assert_eq!(tokens[0].symbol, "T3"); - assert_eq!( - tokens[1].address.to_string(), - "0x0000000000000000000000000000000000000002" - ); - assert_eq!(tokens[1].decimals, 6); - assert_eq!(tokens[1].name, "Token 4"); - assert_eq!(tokens[1].symbol, "T4"); - } - - #[wasm_bindgen_test] - async fn test_get_all_tokens_search_by_name() { - let gui = initialize_gui_with_select_tokens().await; - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - gui.add_record_to_yaml( - "token4".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "6".to_string(), - "Token 4".to_string(), - "T4".to_string(), - ); - - let tokens = gui - .get_all_tokens(Some("Token 3".to_string())) - .await - .unwrap(); - assert_eq!(tokens.len(), 1); - assert_eq!(tokens[0].name, "Token 3"); - } - - #[wasm_bindgen_test] - async fn test_get_all_tokens_search_by_symbol() { - let gui = initialize_gui_with_select_tokens().await; - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - gui.add_record_to_yaml( - "token4".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "6".to_string(), - "Token 4".to_string(), - "T4".to_string(), - ); - - let tokens = gui.get_all_tokens(Some("T4".to_string())).await.unwrap(); - assert_eq!(tokens.len(), 1); - assert_eq!(tokens[0].symbol, "T4"); - } - - #[wasm_bindgen_test] - async fn test_get_all_tokens_search_by_address() { - let gui = initialize_gui_with_select_tokens().await; - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - gui.add_record_to_yaml( - "token4".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "6".to_string(), - "Token 4".to_string(), - "T4".to_string(), - ); - - let tokens = gui - .get_all_tokens(Some( - "0x0000000000000000000000000000000000000002".to_string(), - )) - .await - .unwrap(); - assert_eq!(tokens.len(), 1); - assert_eq!( - tokens[0].address.to_string(), - "0x0000000000000000000000000000000000000002" - ); - } - - #[wasm_bindgen_test] - async fn test_get_all_tokens_search_partial_match() { - let gui = initialize_gui_with_select_tokens().await; - - gui.add_record_to_yaml( - "usdc".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "6".to_string(), - "USD Coin".to_string(), - "USDC".to_string(), - ); - gui.add_record_to_yaml( - "usdt".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - "6".to_string(), - "Tether USD".to_string(), - "USDT".to_string(), - ); - gui.add_record_to_yaml( - "eth".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000003".to_string(), - "18".to_string(), - "Ethereum".to_string(), - "ETH".to_string(), - ); - - let tokens = gui.get_all_tokens(Some("USD".to_string())).await.unwrap(); - assert_eq!(tokens.len(), 2); - - for token in &tokens { - assert!( - token.name.contains("USD") || token.symbol.contains("USD"), - "Token {} should contain 'USD' in name or symbol", - token.symbol - ); - } - - let tokens = gui - .get_all_tokens(Some("000000000000000000000000000000000000000".to_string())) - .await - .unwrap(); - assert_eq!(tokens.len(), 3); - } - - #[wasm_bindgen_test] - async fn test_get_all_tokens_search_empty_string() { - let gui = initialize_gui_with_select_tokens().await; - - gui.add_record_to_yaml( - "token3".to_string(), - "some-network".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - "18".to_string(), - "Token 3".to_string(), - "T3".to_string(), - ); - - let tokens = gui.get_all_tokens(Some("".to_string())).await.unwrap(); - assert_eq!(tokens.len(), 3); - } - } - - #[cfg(not(target_family = "wasm"))] - mod non_wasm_tests { - use crate::gui::{DotrainOrderGui, GuiError}; - use alloy::primitives::Address; - use httpmock::MockServer; - use rain_orderbook_app_settings::spec_version::SpecVersion; - use serde_json::json; - use std::str::FromStr; - - const TEST_YAML_TEMPLATE: &str = r#" -version: {spec_version} -gui: - name: Fixed limit - description: Fixed limit order - short-description: Buy WETH with USDC on Base. - deployments: - some-deployment: - name: Select token deployment - description: Select token deployment description - deposits: - - token: token3 - min: 0 - presets: - - "0" - fields: - - binding: binding-1 - name: Field 1 name - description: Field 1 description - presets: - - name: Preset 1 - value: "0" - - binding: binding-2 - name: Field 2 name - description: Field 2 description - min: 100 - presets: - - value: "0" - select-tokens: - - key: token3 - name: Token 3 - description: Token 3 description - normal-deployment: - name: Normal deployment - description: Normal deployment description - deposits: - - token: token3 - fields: - - binding: binding-1 - name: Field 1 name - default: 10 -networks: - some-network: - rpcs: - - {rpc_url} - chain-id: 123 - network-id: 123 - currency: ETH -subgraphs: - some-sg: https://www.some-sg.com -metaboards: - some-network: https://metaboard.com -rainlangs: - some-rainlang: - network: some-network - address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba -orderbooks: - some-orderbook: - address: 0xc95A5f8eFe14d7a20BD2E5BAFEC4E71f8Ce0B9A6 - network: some-network - subgraph: some-sg - deployment-block: 12345 -scenarios: - some-scenario: - rainlang: some-rainlang - bindings: - test-binding: 5 - scenarios: - sub-scenario: - bindings: - another-binding: 300 -orders: - some-order: - rainlang: some-rainlang - inputs: - - token: token3 - outputs: - - token: token3 -deployments: - some-deployment: - scenario: some-scenario - order: some-order - normal-deployment: - scenario: some-scenario - order: some-order ---- -#test-binding ! -#another-binding ! -#calculate-io -_ _: 0 0; -#handle-io -:; -#handle-add-order -:; -"#; - - #[tokio::test] - async fn test_set_select_token() { - let server = MockServer::start_async().await; - let yaml = TEST_YAML_TEMPLATE - .replace("{rpc_url}", &server.url("/rpc")) - .replace("{spec_version}", &SpecVersion::current().to_string()); - - server.mock(|when, then| { - when.method("POST").path("/rpc").body_contains("252dba42"); - then.json_body(json!({ - "jsonrpc": "2.0", - "id": 1, - "result": "0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e2031000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000", - })); - }); - - let mut gui = DotrainOrderGui::new_with_deployment( - yaml.to_string(), - None, - "some-deployment".to_string(), - None, - ) - .await - .unwrap(); - - let deployment = gui.get_current_deployment().unwrap(); - assert_eq!(deployment.deployment.order.inputs[0].token, None); - assert_eq!(deployment.deployment.order.outputs[0].token, None); - - gui.set_select_token( - "token3".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - ) - .await - .unwrap(); - assert!(gui.is_select_token_set("token3".to_string()).unwrap()); - - let deployment = gui.get_current_deployment().unwrap(); - let token = deployment.deployment.order.inputs[0] - .token - .as_ref() - .unwrap(); - assert_eq!( - token.address, - Address::from_str("0x0000000000000000000000000000000000000001").unwrap() - ); - assert_eq!(token.decimals, Some(6)); - assert_eq!(token.label, Some("Token 1".to_string())); - assert_eq!(token.symbol, Some("T1".to_string())); - - let err = gui - .set_select_token( - "token4".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - ) - .await - .unwrap_err(); - assert_eq!( - err.to_string(), - GuiError::TokenNotFound("token4".to_string()).to_string() - ); - assert_eq!( - err.to_readable_msg(), - "The token 'token4' could not be found in the YAML configuration." - ); - - let mut gui = DotrainOrderGui::new_with_deployment( - yaml.to_string(), - None, - "normal-deployment".to_string(), - None, - ) - .await - .unwrap(); - - let err = gui - .set_select_token( - "token3".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - ) - .await - .unwrap_err(); - assert_eq!(err.to_string(), GuiError::SelectTokensNotSet.to_string()); - assert_eq!( - err.to_readable_msg(), - "No tokens have been configured for selection. Please check your YAML configuration." - ); - } - - #[tokio::test] - async fn test_get_account_balance() { - let server = MockServer::start_async().await; - let yaml = TEST_YAML_TEMPLATE - .replace("{rpc_url}", &server.url("/rpc")) - .replace("{spec_version}", &SpecVersion::current().to_string()); - - server.mock(|when, then| { - when.method("POST").path("/rpc").body_contains("0x313ce567"); - then.json_body(json!({ - "jsonrpc": "2.0", - "id": 1, - "result": "0x0000000000000000000000000000000000000000000000000000000000000012", - })); - }); - server.mock(|when, then| { - when.method("POST").path("/rpc").body_contains("0x70a08231"); - then.json_body(json!({ - "jsonrpc": "2.0", - "id": 1, - "result": "0x00000000000000000000000000000000000000000000000000000000000003e8", - })); - }); - - let gui = DotrainOrderGui::new_with_deployment( - yaml.to_string(), - None, - "some-deployment".to_string(), - None, - ) - .await - .unwrap(); - - let balance = gui - .get_account_balance( - "0x0000000000000000000000000000000000000001".to_string(), - "0x0000000000000000000000000000000000000002".to_string(), - ) - .await - .unwrap(); - - assert_eq!(balance.formatted_balance(), "1e-15"); - } - } -} diff --git a/crates/js_api/src/lib.rs b/crates/js_api/src/lib.rs index f815c3907d..e59b0bb16e 100644 --- a/crates/js_api/src/lib.rs +++ b/crates/js_api/src/lib.rs @@ -1,6 +1,6 @@ #[cfg(target_family = "wasm")] pub mod bindings; -pub mod gui; +pub mod raindex_order_builder; pub mod registry; pub mod yaml; diff --git a/crates/js_api/src/raindex_order_builder/deposits.rs b/crates/js_api/src/raindex_order_builder/deposits.rs new file mode 100644 index 0000000000..2ac84b4815 --- /dev/null +++ b/crates/js_api/src/raindex_order_builder/deposits.rs @@ -0,0 +1,90 @@ +use super::*; +use alloy::primitives::Address; +use rain_orderbook_common::raindex_order_builder::deposits as inner_deposits; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct TokenDeposit { + pub token: String, + pub amount: String, + #[tsify(type = "string")] + pub address: Address, +} +impl_wasm_traits!(TokenDeposit); + +impl From for TokenDeposit { + fn from(td: inner_deposits::TokenDeposit) -> Self { + Self { + token: td.token, + amount: td.amount, + address: td.address, + } + } +} + +#[wasm_export] +impl RaindexOrderBuilder { + #[wasm_export( + js_name = "getDeposits", + unchecked_return_type = "TokenDeposit[]", + return_description = "Array of configured deposits with token info and amounts" + )] + pub fn get_deposits(&self) -> Result, RaindexOrderBuilderWasmError> { + Ok(self + .inner + .get_deposits()? + .into_iter() + .map(Into::into) + .collect()) + } + + #[wasm_export(js_name = "setDeposit", unchecked_return_type = "void")] + pub async fn set_deposit( + &mut self, + #[wasm_export(param_description = "Token key from the YAML configuration")] token: String, + #[wasm_export(param_description = "Deposit amount as a decimal string")] amount: String, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.set_deposit(token, amount).await?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export(js_name = "unsetDeposit", unchecked_return_type = "void")] + pub fn unset_deposit( + &mut self, + #[wasm_export(param_description = "Token key to remove deposit for")] token: String, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.unset_deposit(token)?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export( + js_name = "getDepositPresets", + unchecked_return_type = "string[]", + return_description = "Array of preset deposit amounts" + )] + pub fn get_deposit_presets( + &self, + #[wasm_export(param_description = "Token key to get presets for")] key: String, + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_deposit_presets(key)?) + } + + #[wasm_export( + js_name = "getMissingDeposits", + unchecked_return_type = "string[]", + return_description = "Array of token keys that need deposits" + )] + pub fn get_missing_deposits(&self) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_missing_deposits()?) + } + + #[wasm_export( + js_name = "hasAnyDeposit", + unchecked_return_type = "boolean", + return_description = "Whether any deposit has been set" + )] + pub fn has_any_deposit(&self) -> Result { + Ok(self.inner.has_any_deposit()?) + } +} diff --git a/crates/js_api/src/raindex_order_builder/field_values.rs b/crates/js_api/src/raindex_order_builder/field_values.rs new file mode 100644 index 0000000000..c68024cdb9 --- /dev/null +++ b/crates/js_api/src/raindex_order_builder/field_values.rs @@ -0,0 +1,135 @@ +use super::*; +use rain_orderbook_common::raindex_order_builder::field_values as inner_fv; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct FieldValuePair { + field: String, + value: String, +} +impl_wasm_traits!(FieldValuePair); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[serde(rename_all = "camelCase")] +pub struct PairValue { + pub is_preset: bool, + pub value: String, +} +impl_wasm_traits!(PairValue); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct FieldValue { + pub field: String, + pub value: String, + pub is_preset: bool, +} +impl_wasm_traits!(FieldValue); + +impl RaindexOrderBuilder { + pub fn check_field_values(&mut self) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.check_field_values()?; + Ok(()) + } +} + +#[wasm_export] +impl RaindexOrderBuilder { + #[wasm_export(js_name = "setFieldValue", unchecked_return_type = "void")] + pub fn set_field_value( + &mut self, + #[wasm_export(param_description = "Field identifier from the YAML configuration")] + field: String, + #[wasm_export(param_description = "Value to save (can be a preset value or custom input)")] + value: String, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.set_field_value(field, value)?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export(js_name = "setFieldValues", unchecked_return_type = "void")] + pub fn set_field_values( + &mut self, + #[wasm_export(param_description = "Array of field-value pairs to save")] field_values: Vec< + FieldValuePair, + >, + ) -> Result<(), RaindexOrderBuilderWasmError> { + for fv in field_values { + self.inner.set_field_value(fv.field, fv.value)?; + self.execute_state_update_callback()?; + } + Ok(()) + } + + #[wasm_export(js_name = "unsetFieldValue", unchecked_return_type = "void")] + pub fn unset_field_value( + &mut self, + #[wasm_export(param_description = "Field identifier from the YAML configuration")] + field: String, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.unset_field_value(field)?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export( + js_name = "getFieldValue", + unchecked_return_type = "FieldValue", + return_description = "Field value with the identifier, value, and preset flag" + )] + pub fn get_field_value( + &self, + #[wasm_export(param_description = "Field identifier from the YAML configuration")] + field: String, + ) -> Result { + Ok(self.inner.get_field_value(field)?) + } + + #[wasm_export( + js_name = "getAllFieldValues", + unchecked_return_type = "FieldValue[]", + return_description = "Array of all configured field values" + )] + pub fn get_all_field_values( + &self, + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_all_field_values()?) + } + + #[wasm_export( + js_name = "getFieldDefinition", + unchecked_return_type = "OrderBuilderFieldDefinitionCfg", + return_description = "Complete field configuration" + )] + pub fn get_field_definition( + &self, + #[wasm_export(param_description = "Field binding identifier to look up")] field: &str, + ) -> Result { + Ok(self.inner.get_field_definition(field)?) + } + + #[wasm_export( + js_name = "getAllFieldDefinitions", + unchecked_return_type = "OrderBuilderFieldDefinitionCfg[]", + return_description = "Filtered field definitions" + )] + pub fn get_all_field_definitions( + &self, + #[wasm_export( + param_description = "Optional filter: **true** for fields with defaults, **false** for fields without defaults, **undefined** for all" + )] + filter_defaults: Option, + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_all_field_definitions(filter_defaults)?) + } + + #[wasm_export( + js_name = "getMissingFieldValues", + unchecked_return_type = "OrderBuilderFieldDefinitionCfg[]", + return_description = "Array of field definitions that need to be set" + )] + pub fn get_missing_field_values( + &self, + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_missing_field_values()?) + } +} diff --git a/crates/js_api/src/gui/mod.rs b/crates/js_api/src/raindex_order_builder/mod.rs similarity index 66% rename from crates/js_api/src/gui/mod.rs rename to crates/js_api/src/raindex_order_builder/mod.rs index 24a1531e69..be8fa784df 100644 --- a/crates/js_api/src/gui/mod.rs +++ b/crates/js_api/src/raindex_order_builder/mod.rs @@ -1,36 +1,13 @@ -use alloy::primitives::Address; -use alloy_ethers_typecast::ReadableClientError; -use base64::{engine::general_purpose::URL_SAFE, Engine}; -use flate2::{read::GzDecoder, write::GzEncoder, Compression}; -use rain_math_float::FloatError; -use rain_metaboard_subgraph::metaboard_client::MetaboardSubgraphClientError; -use rain_orderbook_app_settings::{ - deployment::DeploymentCfg, - gui::{ - GuiCfg, GuiDeploymentCfg, GuiFieldDefinitionCfg, GuiPresetCfg, NameAndDescriptionCfg, - ParseGuiConfigSourceError, - }, - order::OrderCfg, - yaml::{ - context::ContextProfile, - dotrain::{DotrainYaml, DotrainYamlValidation}, - emitter, YamlError, YamlParsable, - }, +use rain_orderbook_app_settings::order_builder::{ + NameAndDescriptionCfg, OrderBuilderCfg, OrderBuilderDeploymentCfg, + OrderBuilderFieldDefinitionCfg, }; pub use rain_orderbook_common::erc20::ExtendedTokenInfo; -use rain_orderbook_common::{ - dotrain::{types::patterns::FRONTMATTER_SEPARATOR, RainDocument}, - dotrain_order::{DotrainOrder, DotrainOrderError}, - erc20::ERC20, - utils::amount_formatter::AmountFormatterError, +use rain_orderbook_common::raindex_order_builder::{ + RaindexOrderBuilder as RaindexOrderBuilderInner, RaindexOrderBuilderError, }; use serde::{Deserialize, Serialize}; -use std::io::prelude::*; -use std::{ - collections::BTreeMap, - sync::{Arc, RwLock}, -}; -use strict_yaml_rust::StrictYaml; +use std::collections::BTreeMap; use thiserror::Error; use wasm_bindgen_utils::{impl_wasm_traits, prelude::*, wasm_export}; @@ -39,44 +16,27 @@ mod field_values; mod order_operations; mod select_tokens; mod state_management; -mod validation; -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] #[wasm_bindgen] -pub struct DotrainOrderGui { - dotrain_order: DotrainOrder, - selected_deployment: String, - field_values: BTreeMap, - deposits: BTreeMap, - dotrain_hash: String, +pub struct RaindexOrderBuilder { + pub(crate) inner: RaindexOrderBuilderInner, #[serde(skip)] state_update_callback: Option, } -impl Default for DotrainOrderGui { - fn default() -> Self { - Self { - dotrain_order: DotrainOrder::dummy(), - selected_deployment: "".to_string(), - field_values: BTreeMap::new(), - deposits: BTreeMap::new(), - dotrain_hash: "".to_string(), - state_update_callback: None, - } - } -} #[wasm_export] -impl DotrainOrderGui { - /// Lists all available gui deployment keys from a dotrain YAML file. +impl RaindexOrderBuilder { + /// Lists all available builder deployment keys from a dotrain YAML file. /// - /// This function parses the gui section of the YAML frontmatter to extract deployment keys that can be used - /// to initialize a GUI instance. Use this to build deployment selectors in your UI. + /// This function parses the builder section of the YAML frontmatter to extract deployment keys that can be used + /// to initialize a builder instance. Use this to build deployment selectors in your UI. /// /// ## Examples /// /// ```javascript /// const dotrain = ` - /// gui: + /// builder: /// deployments: /// mainnet-order: /// name: "Mainnet Trading" @@ -84,7 +44,7 @@ impl DotrainOrderGui { /// name: "Test order" /// `; /// - /// const result = await DotrainOrderGui.getDeploymentKeys(dotrain); + /// const result = await RaindexOrderBuilder.getDeploymentKeys(dotrain); /// if (result.error) { /// console.error("Error:", result.error.readableMsg); /// return; @@ -99,21 +59,20 @@ impl DotrainOrderGui { )] pub async fn get_deployment_keys( #[wasm_export( - param_description = "Complete dotrain YAML content including the `gui.deployments` section" + param_description = "Complete dotrain YAML content including the `builder.deployments` section" )] dotrain: String, #[wasm_export( param_description = "Optional additional YAML configuration strings to merge with the frontmatter" )] settings: Option>, - ) -> Result, GuiError> { - let documents = DotrainOrderGui::get_yaml_documents(&dotrain, settings)?; - Ok(GuiCfg::parse_deployment_keys(documents)?) + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(RaindexOrderBuilderInner::get_deployment_keys(dotrain, settings).await?) } - /// Creates a new GUI instance for managing a specific deployment configuration. + /// Creates a new builder instance for managing a specific deployment configuration. /// - /// This is the primary initialization function that sets up the GUI context for a chosen + /// This is the primary initialization function that sets up the builder context for a chosen /// deployment. The instance tracks field values, deposits, token selections, and provides /// methods for generating order transactions. /// @@ -126,15 +85,15 @@ impl DotrainOrderGui { /// /// ```javascript /// // Basic initialization - /// const result = await DotrainOrderGui.newWithDeployment(dotrainYaml, "mainnet-order"); + /// const result = await RaindexOrderBuilder.newWithDeployment(dotrainYaml, "mainnet-order"); /// if (result.error) { /// console.error("Init failed:", result.error.readableMsg); /// return; /// } - /// const gui = result.value; + /// const builder = result.value; /// /// // With state persistence - /// const result = await DotrainOrderGui.newWithDeployment( + /// const result = await RaindexOrderBuilder.newWithDeployment( /// dotrainYaml, /// "mainnet-order", /// (serializedState) => { @@ -142,14 +101,14 @@ impl DotrainOrderGui { /// } /// ); /// if (!result.error) { - /// const gui = result.value; - /// // Use gui instance... + /// const builder = result.value; + /// // Use builder instance... /// } /// ``` #[wasm_export( js_name = "newWithDeployment", preserve_js_class, - return_description = "Initialized GUI instance for further operations" + return_description = "Initialized builder instance for further operations" )] pub async fn new_with_deployment( #[wasm_export(param_description = "Complete dotrain YAML content with all configurations")] @@ -164,44 +123,27 @@ impl DotrainOrderGui { 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.")] + This is useful for auto-saving the state of the builder across sessions.")] state_update_callback: Option, - ) -> Result { - let documents = DotrainOrderGui::get_yaml_documents(&dotrain, settings.clone())?; - - let keys = GuiCfg::parse_deployment_keys(documents.clone())?; - if !keys.contains(&selected_deployment) { - return Err(GuiError::DeploymentNotFound(selected_deployment.clone())); - } - - let dotrain_order = DotrainOrder::create_with_profile( - dotrain.clone(), - settings, - ContextProfile::gui(selected_deployment.clone()), - ) - .await?; - - let dotrain_hash = DotrainOrderGui::compute_state_hash(&dotrain_order)?; - - Ok(DotrainOrderGui { - dotrain_order, - selected_deployment, - field_values: BTreeMap::new(), - deposits: BTreeMap::new(), - dotrain_hash, + ) -> Result { + let inner = + RaindexOrderBuilderInner::new_with_deployment(dotrain, settings, selected_deployment) + .await?; + Ok(RaindexOrderBuilder { + inner, state_update_callback, }) } - /// Retrieves the complete GUI configuration including all deployments. + /// Retrieves the complete builder configuration including all deployments. /// - /// This returns the parsed GUI section from the YAML, filtered to include only + /// This returns the parsed builder section from the YAML, filtered to include only /// the current deployment. Use this to access order-level metadata. /// /// ## Examples /// /// ```javascript - /// const result = gui.getGuiConfig(); + /// const result = builder.getBuilderConfig(); /// if (result.error) { /// console.error("Config error:", result.error.readableMsg); /// return; @@ -210,20 +152,12 @@ impl DotrainOrderGui { /// // Do something with the config /// ``` #[wasm_export( - js_name = "getGuiConfig", - unchecked_return_type = "GuiCfg", - return_description = "Complete GUI configuration with name, description, and deployments" + js_name = "getBuilderConfig", + unchecked_return_type = "OrderBuilderCfg", + return_description = "Complete builder configuration with name, description, and deployments" )] - pub fn get_gui_config(&self) -> Result { - if !GuiCfg::check_gui_key_exists(self.dotrain_order.dotrain_yaml().documents.clone())? { - return Err(GuiError::GuiConfigNotFound); - } - let gui = self - .dotrain_order - .dotrain_yaml() - .get_gui(&self.selected_deployment)? - .ok_or(GuiError::GuiConfigNotFound)?; - Ok(gui) + pub fn get_builder_config(&self) -> Result { + Ok(self.inner.get_builder_config()?) } /// Gets the active deployment's configuration including fields, deposits, and tokens. @@ -241,7 +175,7 @@ impl DotrainOrderGui { /// ## Examples /// /// ```javascript - /// const result = gui.getCurrentDeployment(); + /// const result = builder.getCurrentDeployment(); /// if (result.error) { /// console.error("Error:", result.error.readableMsg); /// return; @@ -251,19 +185,13 @@ impl DotrainOrderGui { /// ``` #[wasm_export( js_name = "getCurrentDeployment", - unchecked_return_type = "GuiDeploymentCfg", + unchecked_return_type = "OrderBuilderDeploymentCfg", return_description = "Active deployment with all configuration details" )] - pub fn get_current_deployment(&self) -> Result { - let gui = self.get_gui_config()?; - let (_, gui_deployment) = gui - .deployments - .into_iter() - .find(|(name, _)| name == &self.selected_deployment) - .ok_or(GuiError::DeploymentNotFound( - self.selected_deployment.clone(), - ))?; - Ok(gui_deployment.clone()) + pub fn get_current_deployment( + &self, + ) -> Result { + Ok(self.inner.get_current_deployment()?) } /// Retrieves detailed token information from YAML configuration or blockchain. @@ -280,7 +208,7 @@ impl DotrainOrderGui { /// /// ```javascript /// // Get token info (may query blockchain) - /// const result = await gui.getTokenInfo("weth"); + /// const result = await builder.getTokenInfo("weth"); /// if (result.error) { /// console.error("Token error:", result.error.readableMsg); /// return; @@ -297,9 +225,8 @@ impl DotrainOrderGui { &self, #[wasm_export(param_description = "Token identifier from the YAML tokens section")] key: String, - ) -> Result { - let token = self.dotrain_order.orderbook_yaml().get_token(&key)?; - Ok(ExtendedTokenInfo::from_token_cfg(&token).await?) + ) -> Result { + Ok(self.inner.get_token_info(key).await?) } /// Gets information for all tokens used in the current deployment's order. @@ -316,7 +243,7 @@ impl DotrainOrderGui { /// ## Examples /// /// ```javascript - /// const result = await gui.getAllTokenInfos(); + /// const result = await builder.getAllTokenInfos(); /// if (result.error) { /// console.error("Error:", result.error.readableMsg); /// return; @@ -329,44 +256,23 @@ impl DotrainOrderGui { unchecked_return_type = "ExtendedTokenInfo[]", return_description = "Array of complete token information" )] - pub async fn get_all_token_infos(&self) -> Result, GuiError> { - let select_tokens = self.get_select_tokens()?; - - let token_keys = match select_tokens.is_empty() { - true => { - let order_key = DeploymentCfg::parse_order_key( - self.dotrain_order.dotrain_yaml().documents, - &self.selected_deployment, - )?; - OrderCfg::parse_io_token_keys( - self.dotrain_order.dotrain_yaml().documents, - &order_key, - )? - } - false => select_tokens - .iter() - .map(|token| token.key.clone()) - .collect(), - }; - - let mut result = Vec::new(); - for key in token_keys.iter() { - result.push(self.get_token_info(key.clone()).await?); - } - Ok(result) + pub async fn get_all_token_infos( + &self, + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_all_token_infos().await?) } /// Extracts order-level metadata from a dotrain configuration. /// - /// This static method allows checking order details without creating a GUI instance, + /// This static method allows checking order details without creating a builder instance, /// useful for displaying order information before deployment selection. /// /// ## Required Fields /// /// The YAML must contain: - /// - `gui.name` - Order display name - /// - `gui.description` - Full order description - /// - `gui.short-description` - Brief summary (optional but recommended) + /// - `builder.name` - Order display name + /// - `builder.description` - Full order description + /// - `builder.short-description` - Brief summary (optional but recommended) /// /// ## Examples /// @@ -390,9 +296,10 @@ impl DotrainOrderGui { param_description = "Optional additional YAML configuration strings to merge with the frontmatter" )] settings: Option>, - ) -> Result { - let documents = DotrainOrderGui::get_yaml_documents(&dotrain, settings)?; - Ok(GuiCfg::parse_order_details(documents)?) + ) -> Result { + Ok(RaindexOrderBuilderInner::get_order_details( + dotrain, settings, + )?) } /// Gets metadata for all deployments defined in the configuration. @@ -433,9 +340,10 @@ impl DotrainOrderGui { param_description = "Optional additional YAML configuration strings to merge with the frontmatter" )] settings: Option>, - ) -> Result, GuiError> { - let documents = DotrainOrderGui::get_yaml_documents(&dotrain, settings)?; - Ok(GuiCfg::parse_deployment_details(documents)?) + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(RaindexOrderBuilderInner::get_deployment_details( + dotrain, settings, + )?) } /// Gets metadata for a specific deployment by key. @@ -466,12 +374,10 @@ impl DotrainOrderGui { )] settings: Option>, #[wasm_export(param_description = "Deployment identifier to look up")] key: String, - ) -> Result { - let deployment_details = DotrainOrderGui::get_deployment_details(dotrain, settings)?; - let deployment_detail = deployment_details - .get(&key) - .ok_or(GuiError::DeploymentNotFound(key))?; - Ok(deployment_detail.clone()) + ) -> Result { + Ok(RaindexOrderBuilderInner::get_deployment_detail( + dotrain, settings, key, + )?) } /// Gets metadata for the currently active deployment. @@ -482,7 +388,7 @@ impl DotrainOrderGui { /// ## Examples /// /// ```javascript - /// const result = gui.getCurrentDeploymentDetails(); + /// const result = builder.getCurrentDeploymentDetails(); /// if (result.error) { /// console.error("Error:", result.error.readableMsg); /// return; @@ -495,15 +401,10 @@ impl DotrainOrderGui { unchecked_return_type = "NameAndDescriptionCfg", return_description = "Current deployment's metadata" )] - pub fn get_current_deployment_details(&self) -> Result { - let deployment_details = - GuiCfg::parse_deployment_details(self.dotrain_order.dotrain_yaml().documents.clone())?; - Ok(deployment_details - .get(&self.selected_deployment) - .ok_or(GuiError::DeploymentNotFound( - self.selected_deployment.clone(), - ))? - .clone()) + pub fn get_current_deployment_details( + &self, + ) -> Result { + Ok(self.inner.get_current_deployment_details()?) } /// Exports the current configuration as a complete dotrain text file. @@ -515,7 +416,7 @@ impl DotrainOrderGui { /// /// The output follows the standard dotrain format: /// ```text - /// gui: + /// builder: /// ... /// --- /// #binding1 !The binding value @@ -526,7 +427,7 @@ impl DotrainOrderGui { /// ## Examples /// /// ```javascript - /// const result = gui.generateDotrainText(); + /// const result = builder.generateDotrainText(); /// if (result.error) { /// console.error("Export failed:", result.error.readableMsg); /// return; @@ -539,15 +440,8 @@ impl DotrainOrderGui { unchecked_return_type = "string", return_description = "Complete dotrain content with YAML frontmatter separator" )] - pub fn generate_dotrain_text(&self) -> Result { - let rain_document = RainDocument::create(self.dotrain_order.dotrain()?, None, None, None); - let dotrain = format!( - "{}\n{}\n{}", - emitter::emit_documents(&self.dotrain_order.dotrain_yaml().documents)?, - FRONTMATTER_SEPARATOR, - rain_document.body() - ); - Ok(dotrain) + pub fn generate_dotrain_text(&self) -> Result { + Ok(self.inner.generate_dotrain_text()?) } /// Composes the final Rainlang code with all bindings and scenarios applied. @@ -562,7 +456,7 @@ impl DotrainOrderGui { /// ## Examples /// /// ```javascript - /// const result = await gui.getComposedRainlang(); + /// const result = await builder.getComposedRainlang(); /// if (result.error) { /// console.error("Composition error:", result.error.readableMsg); /// return; @@ -575,241 +469,39 @@ impl DotrainOrderGui { unchecked_return_type = "string", return_description = "Composed Rainlang code with comments for each entrypoint" )] - pub async fn get_composed_rainlang(&mut self) -> Result { - self.update_scenario_bindings()?; - let dotrain = self.generate_dotrain_text()?; - let deployment = self.get_current_deployment()?; - let dotrain_order = DotrainOrder::create_with_profile( - dotrain.clone(), - None, - ContextProfile::gui(deployment.deployment.key.clone()), - ) - .await?; - let rainlang = dotrain_order - .compose_deployment_to_rainlang(self.selected_deployment.clone()) - .await?; - Ok(rainlang) - } -} -impl DotrainOrderGui { - pub fn get_yaml_documents( - dotrain: &str, - settings: Option>, - ) -> Result>>, GuiError> { - let frontmatter = RainDocument::get_front_matter(dotrain) - .unwrap_or("") - .to_string(); - let mut sources = vec![frontmatter]; - if let Some(settings) = settings { - sources.extend(settings); - } - - let dotrain_yaml = DotrainYaml::new(sources, DotrainYamlValidation::default())?; - Ok(dotrain_yaml.documents) + pub async fn get_composed_rainlang(&mut self) -> Result { + Ok(self.inner.get_composed_rainlang().await?) } } #[derive(Error, Debug)] -pub enum GuiError { - #[error("Gui config not found")] - GuiConfigNotFound, - #[error("Deployment not found: {0}")] - DeploymentNotFound(String), - #[error("Field binding not found: {0}")] - FieldBindingNotFound(String), - #[error("Missing field value: {0}")] - FieldValueNotSet(String), - #[error("Deposit token not found in gui config: {0}")] - DepositTokenNotFound(String), - #[error("Missing deposit with token: {0}")] - DepositNotSet(String), - #[error("Missing deposit token for current deployment: {0}")] - MissingDepositToken(String), - #[error("Deposit amount cannot be an empty string")] - DepositAmountCannotBeEmpty, - #[error("Orderbook not found")] - OrderbookNotFound, - #[error("Order not found: {0}")] - OrderNotFound(String), - #[error("Deserialized dotrain mismatch")] - DotrainMismatch, - #[error("Vault id not found for output index: {0}")] - VaultIdNotFound(String), - #[error("Rainlang not found")] - RainlangNotFound, - #[error("Token not found {0}")] - TokenNotFound(String), - #[error("Invalid preset")] - InvalidPreset, - #[error("Presets not set")] - PresetsNotSet, - #[error("Select tokens not set")] - SelectTokensNotSet, - #[error("Token must be selected: {0}")] - TokenMustBeSelected(String), - #[error("Binding has no presets: {0}")] - BindingHasNoPresets(String), - #[error("Token not in select tokens: {0}")] - TokenNotInSelectTokens(String), +pub enum RaindexOrderBuilderWasmError { + #[error(transparent)] + BuilderError(#[from] RaindexOrderBuilderError), #[error("JavaScript error: {0}")] JsError(String), #[error(transparent)] - DotrainOrderError(#[from] DotrainOrderError), - #[error(transparent)] - ParseGuiConfigSourceError(#[from] ParseGuiConfigSourceError), - #[error(transparent)] - IoError(#[from] std::io::Error), - #[error(transparent)] - BincodeError(#[from] bincode::Error), - #[error(transparent)] - Base64Error(#[from] base64::DecodeError), - #[error(transparent)] - FromHexError(#[from] alloy::hex::FromHexError), - #[error(transparent)] - ReadableClientError(#[from] ReadableClientError), - #[error(transparent)] - DepositError(#[from] rain_orderbook_common::deposit::DepositError), - #[error(transparent)] - ParseError(#[from] alloy::primitives::ruint::ParseError), - #[error(transparent)] - ReadContractParametersBuilderError( - #[from] alloy_ethers_typecast::ReadContractParametersBuilderError, - ), - #[error(transparent)] - UnitsError(#[from] alloy::primitives::utils::UnitsError), - #[error(transparent)] - WritableTransactionExecuteError( - #[from] rain_orderbook_common::transaction::WritableTransactionExecuteError, - ), - #[error(transparent)] - AddOrderArgsError(#[from] rain_orderbook_common::add_order::AddOrderArgsError), - #[error(transparent)] - ERC20Error(#[from] rain_orderbook_common::erc20::Error), - #[error(transparent)] - SolTypesError(#[from] alloy::sol_types::Error), - #[error(transparent)] SerdeWasmBindgenError(#[from] serde_wasm_bindgen::Error), - #[error(transparent)] - YamlError(#[from] YamlError), - #[error(transparent)] - ValidationError(#[from] validation::GuiValidationError), - #[error(transparent)] - UrlParseError(#[from] url::ParseError), - #[error(transparent)] - AmountFormatterError(#[from] AmountFormatterError), - #[error(transparent)] - FloatError(#[from] FloatError), - #[error(transparent)] - RainMetadataError(#[from] rain_metadata::Error), - #[error("No address found in metaboard subgraph")] - NoAddressInMetaboardSubgraph, - #[error(transparent)] - MetaboardSubgraphClientError(#[from] MetaboardSubgraphClientError), } -impl GuiError { +impl RaindexOrderBuilderWasmError { pub fn to_readable_msg(&self) -> String { match self { - GuiError::GuiConfigNotFound => - "The GUI configuration could not be found. Please check your YAML configuration file.".to_string(), - GuiError::DeploymentNotFound(name) => - format!("The deployment '{}' could not be found. Please select a valid deployment from your YAML configuration.", name), - GuiError::FieldBindingNotFound(field) => - format!("The field binding '{}' could not be found in the YAML configuration.", field), - GuiError::FieldValueNotSet(field) => - format!("The value for field '{}' is required but has not been set.", field), - GuiError::DepositTokenNotFound(token) => - format!("The deposit token '{}' was not found in the YAML configuration.", token), - GuiError::DepositNotSet(token) => - format!("A deposit for token '{}' is required but has not been set.", token), - GuiError::MissingDepositToken(deployment) => - format!("A deposit for token is required but has not been set for deployment '{}'.", deployment), - GuiError::DepositAmountCannotBeEmpty => - "The deposit amount cannot be an empty string. Please set a valid amount.".to_string(), - GuiError::OrderbookNotFound => - "The orderbook configuration could not be found. Please check your YAML configuration.".to_string(), - GuiError::OrderNotFound(order) => - format!("The order '{}' could not be found in the YAML configuration.", order), - GuiError::DotrainMismatch => - "There was a mismatch in the dotrain configuration. Please check your YAML configuration for consistency.".to_string(), - GuiError::VaultIdNotFound(index) => - format!("The vault ID for output index '{}' could not be found in the YAML configuration.", index), - GuiError::RainlangNotFound => - "The rainlang configuration could not be found. Please check your YAML configuration.".to_string(), - GuiError::TokenNotFound(token) => - format!("The token '{}' could not be found in the YAML configuration.", token), - GuiError::InvalidPreset => - "The selected preset is invalid. Please choose a different preset from your YAML configuration.".to_string(), - GuiError::PresetsNotSet => - "No presets have been configured. Please check your YAML configuration.".to_string(), - GuiError::SelectTokensNotSet => - "No tokens have been configured for selection. Please check your YAML configuration.".to_string(), - GuiError::TokenMustBeSelected(token) => - format!("The token '{}' must be selected to proceed.", token), - GuiError::BindingHasNoPresets(binding) => - format!("The binding '{}' does not have any presets configured in the YAML configuration.", binding), - GuiError::TokenNotInSelectTokens(token) => - format!("The token '{}' is not in the list of selectable tokens defined in the YAML configuration.", token), - GuiError::JsError(msg) => - format!("A JavaScript error occurred: {}", msg), - GuiError::DotrainOrderError(err) => - format!("Order configuration error in YAML: {}", err), - GuiError::ParseGuiConfigSourceError(err) => - format!("Failed to parse YAML GUI configuration: {}", err), - GuiError::IoError(err) => - format!("I/O error: {}", err), - GuiError::BincodeError(err) => - format!("Data serialization error: {}", err), - GuiError::Base64Error(err) => - format!("Base64 encoding/decoding error: {}", err), - GuiError::FromHexError(err) => - format!("Invalid hexadecimal value: {}", err), - GuiError::ReadableClientError(err) => - format!("Network client error: {}", err), - GuiError::DepositError(err) => - format!("Deposit error: {}", err), - GuiError::ParseError(err) => - format!("Number parsing error: {}", err), - GuiError::ReadContractParametersBuilderError(err) => - format!("Contract parameter error: {}", err), - GuiError::UnitsError(err) => - format!("Unit conversion error: {}", err), - GuiError::WritableTransactionExecuteError(err) => - format!("Transaction execution error: {}", err), - GuiError::AddOrderArgsError(err) => - format!("Invalid order arguments: {}", err), - GuiError::ERC20Error(err) => - format!("ERC20 token error: {}", err), - GuiError::SolTypesError(err) => - format!("Solidity type error: {}", err), - GuiError::SerdeWasmBindgenError(err) => - format!("Data serialization error: {}", err), - GuiError::YamlError(err) => format!("YAML configuration error: {}", err), - GuiError::ValidationError(err) => format!("Validation error: {}", err), - GuiError::UrlParseError(err) => format!("URL parsing error: {err}"), - GuiError::AmountFormatterError(err) => - format!("There was a problem formatting the amount: {err}"), - GuiError::FloatError(err) => { - format!("There was a problem with the float value: {err}") - } - GuiError::RainMetadataError(err) => - format!("There was a problem with the rain metadata: {err}"), - GuiError::NoAddressInMetaboardSubgraph => - "No address was found in the metaboard subgraph response.".to_string(), - GuiError::MetaboardSubgraphClientError(err) => - format!("There was a problem with the metaboard subgraph client: {err}"), + Self::BuilderError(err) => err.to_readable_msg(), + Self::JsError(msg) => format!("A JavaScript error occurred: {}", msg), + Self::SerdeWasmBindgenError(err) => format!("Data serialization error: {}", err), } } } -impl From for JsValue { - fn from(value: GuiError) -> Self { +impl From for JsValue { + fn from(value: RaindexOrderBuilderWasmError) -> Self { JsError::new(&value.to_string()).into() } } -impl From for WasmEncodedError { - fn from(value: GuiError) -> Self { +impl From for WasmEncodedError { + fn from(value: RaindexOrderBuilderWasmError) -> Self { WasmEncodedError { msg: value.to_string(), readable_msg: value.to_readable_msg(), @@ -820,15 +512,16 @@ impl From for WasmEncodedError { #[cfg(test)] mod tests { use super::*; + use rain_orderbook_app_settings::order_builder::OrderBuilderPresetCfg; use rain_orderbook_app_settings::spec_version::SpecVersion; - use rain_orderbook_app_settings::yaml::FieldErrorKind; + use rain_orderbook_app_settings::yaml::{FieldErrorKind, YamlError}; use wasm_bindgen_test::wasm_bindgen_test; pub fn get_yaml() -> String { format!( r#" version: {spec_version} -gui: +builder: name: Fixed limit description: Fixed limit order short-description: Buy WETH with USDC on Base. @@ -933,7 +626,7 @@ subgraphs: metaboards: some-network: https://metaboard.com rainlangs: - some-rainlang: + some-deployer: network: some-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba orderbooks: @@ -958,7 +651,7 @@ tokens: symbol: T2 scenarios: some-scenario: - rainlang: some-rainlang + rainlang: some-deployer bindings: test-binding: 5 scenarios: @@ -973,14 +666,14 @@ orders: outputs: - token: token2 vault-id: 1 - rainlang: some-rainlang + rainlang: some-deployer orderbook: some-orderbook other-order: inputs: - token: token1 outputs: - token: token1 - rainlang: some-rainlang + rainlang: some-deployer orderbook: some-orderbook deployments: some-deployment: @@ -1010,7 +703,7 @@ _ _: 0 0; format!( r#" version: {spec_version} -gui: +builder: name: Validation Test description: Test deployment with various validation rules deployments: @@ -1162,7 +855,7 @@ networks: subgraphs: test-sg: https://test.subgraph.com rainlangs: - test-rainlang: + test-deployer: network: test-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba orderbooks: @@ -1211,7 +904,7 @@ tokens: symbol: T6 scenarios: test-scenario: - rainlang: test-rainlang + rainlang: test-deployer bindings: test: 1 orders: @@ -1222,7 +915,7 @@ orders: outputs: - token: token1 vault-id: 1 - rainlang: test-rainlang + rainlang: test-deployer orderbook: test-orderbook deployments: validation-deployment: @@ -1241,8 +934,8 @@ _ _: 0 0; ) } - pub async fn initialize_gui(deployment_name: Option) -> DotrainOrderGui { - DotrainOrderGui::new_with_deployment( + pub async fn initialize_builder(deployment_name: Option) -> RaindexOrderBuilder { + RaindexOrderBuilder::new_with_deployment( get_yaml(), None, deployment_name.unwrap_or("some-deployment".to_string()), @@ -1252,8 +945,8 @@ _ _: 0 0; .unwrap() } - pub async fn initialize_gui_with_select_tokens() -> DotrainOrderGui { - DotrainOrderGui::new_with_deployment( + pub async fn initialize_builder_with_select_tokens() -> RaindexOrderBuilder { + RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "select-token-deployment".to_string(), @@ -1263,8 +956,8 @@ _ _: 0 0; .unwrap() } - pub async fn initialize_validation_gui() -> DotrainOrderGui { - DotrainOrderGui::new_with_deployment( + pub async fn initialize_validation_builder() -> RaindexOrderBuilder { + RaindexOrderBuilder::new_with_deployment( get_yaml_with_validation(), None, "validation-deployment".to_string(), @@ -1276,7 +969,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_get_deployment_keys() { - let deployment_keys = DotrainOrderGui::get_deployment_keys(get_yaml(), None) + let deployment_keys = RaindexOrderBuilder::get_deployment_keys(get_yaml(), None) .await .unwrap(); assert_eq!( @@ -1291,7 +984,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_new_with_deployment() { - let res = DotrainOrderGui::new_with_deployment( + let res = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1300,7 +993,7 @@ _ _: 0 0; .await; assert!(res.is_ok()); - let err = DotrainOrderGui::new_with_deployment( + let err = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "invalid-deployment".to_string(), @@ -1310,14 +1003,15 @@ _ _: 0 0; .unwrap_err(); assert_eq!( err.to_string(), - GuiError::DeploymentNotFound("invalid-deployment".to_string()).to_string() + RaindexOrderBuilderError::DeploymentNotFound("invalid-deployment".to_string()) + .to_string() ); assert_eq!(err.to_readable_msg(), "The deployment 'invalid-deployment' could not be found. Please select a valid deployment from your YAML configuration."); } #[wasm_bindgen_test] - async fn test_get_gui_config() { - let gui = DotrainOrderGui::new_with_deployment( + async fn test_get_builder_config() { + let builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1326,11 +1020,11 @@ _ _: 0 0; .await .unwrap(); - let gui_config = gui.get_gui_config().unwrap(); - assert_eq!(gui_config.name, "Fixed limit".to_string()); - assert_eq!(gui_config.description, "Fixed limit order".to_string()); - assert_eq!(gui_config.deployments.len(), 1); - let deployment = gui_config.deployments.get("some-deployment").unwrap(); + let builder_config = builder.get_builder_config().unwrap(); + assert_eq!(builder_config.name, "Fixed limit".to_string()); + assert_eq!(builder_config.description, "Fixed limit order".to_string()); + assert_eq!(builder_config.deployments.len(), 1); + let deployment = builder_config.deployments.get("some-deployment").unwrap(); assert_eq!(deployment.name, "Buy WETH with USDC on Base.".to_string()); assert_eq!( deployment.description, @@ -1357,17 +1051,17 @@ _ _: 0 0; assert_eq!( field.presets, Some(vec![ - GuiPresetCfg { + OrderBuilderPresetCfg { id: "0".to_string(), name: Some("Preset 1".to_string()), value: "0x1234567890abcdef1234567890abcdef12345678".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "1".to_string(), name: Some("Preset 2".to_string()), value: "false".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "2".to_string(), name: Some("Preset 3".to_string()), value: "some-string".to_string(), @@ -1381,17 +1075,17 @@ _ _: 0 0; assert_eq!( field.presets, Some(vec![ - GuiPresetCfg { + OrderBuilderPresetCfg { id: "0".to_string(), name: None, value: "99.2".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "1".to_string(), name: None, value: "582.1".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "2".to_string(), name: None, value: "648.239".to_string(), @@ -1402,7 +1096,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_get_current_deployment() { - let gui = DotrainOrderGui::new_with_deployment( + let builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1411,7 +1105,7 @@ _ _: 0 0; .await .unwrap(); - let deployment = gui.get_current_deployment().unwrap(); + let deployment = builder.get_current_deployment().unwrap(); assert_eq!(deployment.name, "Buy WETH with USDC on Base.".to_string()); assert_eq!( deployment.description, @@ -1438,17 +1132,17 @@ _ _: 0 0; assert_eq!( field.presets, Some(vec![ - GuiPresetCfg { + OrderBuilderPresetCfg { id: "0".to_string(), name: Some("Preset 1".to_string()), value: "0x1234567890abcdef1234567890abcdef12345678".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "1".to_string(), name: Some("Preset 2".to_string()), value: "false".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "2".to_string(), name: Some("Preset 3".to_string()), value: "some-string".to_string(), @@ -1462,17 +1156,17 @@ _ _: 0 0; assert_eq!( field.presets, Some(vec![ - GuiPresetCfg { + OrderBuilderPresetCfg { id: "0".to_string(), name: None, value: "99.2".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "1".to_string(), name: None, value: "582.1".to_string(), }, - GuiPresetCfg { + OrderBuilderPresetCfg { id: "2".to_string(), name: None, value: "648.239".to_string(), @@ -1483,7 +1177,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_get_token_info_local() { - let gui = DotrainOrderGui::new_with_deployment( + let builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1492,7 +1186,7 @@ _ _: 0 0; .await .unwrap(); - let token1_info = gui.get_token_info("token1".to_string()).await.unwrap(); + let token1_info = builder.get_token_info("token1".to_string()).await.unwrap(); assert_eq!( token1_info.address.to_string(), "0xc2132D05D31c914a87C6611C10748AEb04B58e8F" @@ -1501,7 +1195,7 @@ _ _: 0 0; assert_eq!(token1_info.name, "Token 1"); assert_eq!(token1_info.symbol, "T1"); - let token2_info = gui.get_token_info("token2".to_string()).await.unwrap(); + let token2_info = builder.get_token_info("token2".to_string()).await.unwrap(); assert_eq!( token2_info.address.to_string(), "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063" @@ -1510,7 +1204,7 @@ _ _: 0 0; assert_eq!(token2_info.name, "Token 2"); assert_eq!(token2_info.symbol, "T2"); - let err = gui + let err = builder .get_token_info("invalid-token".to_string()) .await .unwrap_err(); @@ -1526,7 +1220,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_get_all_token_infos_local() { - let gui = DotrainOrderGui::new_with_deployment( + let builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1535,7 +1229,7 @@ _ _: 0 0; .await .unwrap(); - let token_infos = gui.get_all_token_infos().await.unwrap(); + let token_infos = builder.get_all_token_infos().await.unwrap(); assert_eq!(token_infos.len(), 2); assert_eq!( token_infos[0].address.to_string(), @@ -1555,7 +1249,7 @@ _ _: 0 0; #[wasm_bindgen_test] fn test_get_order_details() { - let order_details = DotrainOrderGui::get_order_details(get_yaml(), None).unwrap(); + let order_details = RaindexOrderBuilder::get_order_details(get_yaml(), None).unwrap(); assert_eq!(order_details.name, "Fixed limit"); assert_eq!(order_details.description, "Fixed limit order"); assert_eq!( @@ -1566,7 +1260,7 @@ _ _: 0 0; let yaml = format!( r#" version: {spec_version} -gui: +builder: test: test --- #calculate-io @@ -1578,24 +1272,24 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_order_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_order_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { kind: FieldErrorKind::Missing("name".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Missing required field 'name' in gui" + "YAML configuration error: Missing required field 'name' in builder" ); let yaml = format!( r#" version: {spec_version} -gui: +builder: name: Test name --- #calculate-io @@ -1607,24 +1301,24 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_order_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_order_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { kind: FieldErrorKind::Missing("description".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Missing required field 'description' in gui" + "YAML configuration error: Missing required field 'description' in builder" ); let yaml = format!( r#" version: {spec_version} -gui: +builder: name: Test name description: Test description --- @@ -1637,24 +1331,25 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_order_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_order_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { kind: FieldErrorKind::Missing("short-description".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Missing required field 'short-description' in gui" + "YAML configuration error: Missing required field 'short-description' in builder" ); } #[wasm_bindgen_test] fn test_get_deployment_details() { - let deployment_details = DotrainOrderGui::get_deployment_details(get_yaml(), None).unwrap(); + let deployment_details = + RaindexOrderBuilder::get_deployment_details(get_yaml(), None).unwrap(); assert_eq!(deployment_details.len(), 3); let deployment_detail = deployment_details.get("some-deployment").unwrap(); assert_eq!(deployment_detail.name, "Buy WETH with USDC on Base."); @@ -1692,13 +1387,13 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let details = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap(); + let details = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap(); assert_eq!(details.len(), 0); let yaml = format!( r#" version: {spec_version} -gui: +builder: test: test --- #calculate-io @@ -1710,24 +1405,24 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Missing required field 'deployments' in gui" + "YAML configuration error: Missing required field 'deployments' in builder" ); let yaml = format!( r#" version: {spec_version} -gui: +builder: deployments: test --- #calculate-io @@ -1739,7 +1434,7 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { @@ -1747,19 +1442,19 @@ _ _: 0 0; field: "deployments".to_string(), expected: "a map".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Field 'deployments' must be a map in gui" + "YAML configuration error: Field 'deployments' must be a map in builder" ); let yaml = format!( r#" version: {spec_version} -gui: +builder: deployments: - test --- @@ -1772,7 +1467,7 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { @@ -1780,19 +1475,19 @@ _ _: 0 0; field: "deployments".to_string(), expected: "a map".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Field 'deployments' must be a map in gui" + "YAML configuration error: Field 'deployments' must be a map in builder" ); let yaml = format!( r#" version: {spec_version} -gui: +builder: deployments: test: test --- @@ -1805,13 +1500,13 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let details = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap(); + let details = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap(); assert_eq!(details.len(), 0); let yaml = format!( r#" version: {spec_version} -gui: +builder: deployments: test: unknown-field: value @@ -1825,24 +1520,24 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { kind: FieldErrorKind::Missing("name".to_string()), - location: "gui deployment 'test'".to_string(), + location: "builder deployment 'test'".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Missing required field 'name' in gui deployment 'test'" + "YAML configuration error: Missing required field 'name' in builder deployment 'test'" ); let yaml = format!( r#" version: {spec_version} -gui: +builder: deployments: test: name: Test name @@ -1856,26 +1551,29 @@ _ _: 0 0; "#, spec_version = SpecVersion::current() ); - let err = DotrainOrderGui::get_deployment_details(yaml.to_string(), None).unwrap_err(); + let err = RaindexOrderBuilder::get_deployment_details(yaml.to_string(), None).unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { kind: FieldErrorKind::Missing("description".to_string()), - location: "gui deployment 'test'".to_string(), + location: "builder deployment 'test'".to_string(), } .to_string() ); assert_eq!( err.to_readable_msg(), - "YAML configuration error: Missing required field 'description' in gui deployment 'test'" + "YAML configuration error: Missing required field 'description' in builder deployment 'test'" ); } #[wasm_bindgen_test] fn test_get_deployment_detail() { - let deployment_detail = - DotrainOrderGui::get_deployment_detail(get_yaml(), None, "some-deployment".to_string()) - .unwrap(); + let deployment_detail = RaindexOrderBuilder::get_deployment_detail( + get_yaml(), + None, + "some-deployment".to_string(), + ) + .unwrap(); assert_eq!(deployment_detail.name, "Buy WETH with USDC on Base."); assert_eq!( deployment_detail.description, @@ -1889,7 +1587,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_get_current_deployment_detail() { - let gui = DotrainOrderGui::new_with_deployment( + let builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1898,7 +1596,7 @@ _ _: 0 0; .await .unwrap(); - let deployment_detail = gui.get_current_deployment_details().unwrap(); + let deployment_detail = builder.get_current_deployment_details().unwrap(); assert_eq!(deployment_detail.name, "Buy WETH with USDC on Base."); assert_eq!( deployment_detail.description, @@ -1912,7 +1610,7 @@ _ _: 0 0; #[wasm_bindgen_test] async fn test_generate_dotrain_text() { - let gui = DotrainOrderGui::new_with_deployment( + let builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1920,10 +1618,10 @@ _ _: 0 0; ) .await .unwrap(); - let original_current_deployment = gui.get_current_deployment_details().unwrap(); + let original_current_deployment = builder.get_current_deployment_details().unwrap(); - let dotrain_text = gui.generate_dotrain_text().unwrap(); - let gui = DotrainOrderGui::new_with_deployment( + let dotrain_text = builder.generate_dotrain_text().unwrap(); + let builder = RaindexOrderBuilder::new_with_deployment( dotrain_text, None, "some-deployment".to_string(), @@ -1931,14 +1629,14 @@ _ _: 0 0; ) .await .unwrap(); - let new_current_deployment = gui.get_current_deployment_details().unwrap(); + let new_current_deployment = builder.get_current_deployment_details().unwrap(); assert_eq!(new_current_deployment, original_current_deployment); } #[wasm_bindgen_test] async fn test_get_composed_rainlang() { - let mut gui = DotrainOrderGui::new_with_deployment( + let mut builder = RaindexOrderBuilder::new_with_deployment( get_yaml(), None, "some-deployment".to_string(), @@ -1947,7 +1645,7 @@ _ _: 0 0; .await .unwrap(); - let rainlang = gui.get_composed_rainlang().await.unwrap(); + let rainlang = builder.get_composed_rainlang().await.unwrap(); let expected_rainlang = "/* 0. calculate-io */ \n_ _: 0 0;\n\n/* 1. handle-io */ \n:;".to_string(); assert_eq!(rainlang, expected_rainlang); @@ -1960,7 +1658,7 @@ _ _: 0 0; use serde_json::json; pub const SELECT_TOKEN_YAML: &str = r#" -gui: +builder: name: Fixed limit description: Fixed limit order order short-description: Buy WETH with USDC on Base. @@ -1995,7 +1693,7 @@ subgraphs: metaboards: some-network: https://metaboard.com rainlangs: - some-rainlang: + some-deployer: network: some-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba orderbooks: @@ -2006,7 +1704,7 @@ orderbooks: deployment-block: 12345 scenarios: some-scenario: - rainlang: some-rainlang + rainlang: some-deployer bindings: test-binding: 5 scenarios: @@ -2015,7 +1713,7 @@ scenarios: another-binding: 300 orders: some-order: - rainlang: some-rainlang + rainlang: some-deployer inputs: - token: token3 outputs: @@ -2067,7 +1765,7 @@ networks: })); }); - let mut gui = DotrainOrderGui::new_with_deployment( + let mut builder = RaindexOrderBuilder::new_with_deployment( yaml.to_string(), None, "some-deployment".to_string(), @@ -2076,7 +1774,10 @@ networks: .await .unwrap(); - let err = gui.get_token_info("token3".to_string()).await.unwrap_err(); + let err = builder + .get_token_info("token3".to_string()) + .await + .unwrap_err(); assert_eq!( err.to_string(), YamlError::Field { @@ -2090,14 +1791,15 @@ networks: "YAML configuration error: Missing required field 'tokens' in root" ); - gui.set_select_token( - "token3".to_string(), - "0x0000000000000000000000000000000000000001".to_string(), - ) - .await - .unwrap(); + builder + .set_select_token( + "token3".to_string(), + "0x0000000000000000000000000000000000000001".to_string(), + ) + .await + .unwrap(); - let token_info = gui.get_token_info("token3".to_string()).await.unwrap(); + let token_info = builder.get_token_info("token3".to_string()).await.unwrap(); assert_eq!( token_info.address.to_string(), "0x0000000000000000000000000000000000000001" @@ -2106,7 +1808,7 @@ networks: assert_eq!(token_info.name, "Token 1"); assert_eq!(token_info.symbol, "T1"); - let token_infos = gui.get_all_token_infos().await.unwrap(); + let token_infos = builder.get_all_token_infos().await.unwrap(); assert_eq!(token_infos.len(), 1); assert_eq!( token_infos[0].address.to_string(), diff --git a/crates/js_api/src/raindex_order_builder/order_operations.rs b/crates/js_api/src/raindex_order_builder/order_operations.rs new file mode 100644 index 0000000000..897e627b05 --- /dev/null +++ b/crates/js_api/src/raindex_order_builder/order_operations.rs @@ -0,0 +1,206 @@ +use super::*; +use alloy::primitives::{Address, Bytes, U256}; +use rain_orderbook_app_settings::order::VaultType; +use rain_orderbook_common::raindex_order_builder::order_operations as inner_ops; +use std::collections::HashMap; + +#[derive(Serialize, Deserialize, Debug, Clone, Tsify)] +pub struct TokenAllowance { + #[tsify(type = "string")] + pub token: Address, + #[tsify(type = "string")] + pub allowance: U256, +} +impl_wasm_traits!(TokenAllowance); + +#[derive(Serialize, Deserialize, Debug, Clone, Tsify)] +pub struct AllowancesResult(pub Vec); +impl_wasm_traits!(AllowancesResult); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub enum ApprovalCalldataResult { + NoDeposits, + Calldatas(Vec), +} +impl_wasm_traits!(ApprovalCalldataResult); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct ApprovalCalldata { + #[tsify(type = "string")] + pub token: Address, + #[tsify(type = "string")] + pub calldata: Bytes, +} +impl_wasm_traits!(ApprovalCalldata); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub enum DepositCalldataResult { + NoDeposits, + Calldatas(#[tsify(type = "string[]")] Vec), +} +impl_wasm_traits!(DepositCalldataResult); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct AddOrderCalldataResult(#[tsify(type = "string")] pub Bytes); +impl_wasm_traits!(AddOrderCalldataResult); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct DepositAndAddOrderCalldataResult(#[tsify(type = "string")] pub Bytes); +impl_wasm_traits!(DepositAndAddOrderCalldataResult); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct IOVaultIds( + #[tsify(type = "Map>")] + pub HashMap>>, +); +impl_wasm_traits!(IOVaultIds); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct WithdrawCalldataResult(#[tsify(type = "string[]")] pub Vec); +impl_wasm_traits!(WithdrawCalldataResult); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +pub struct ExtendedApprovalCalldata { + #[tsify(type = "string")] + pub token: Address, + #[tsify(type = "string")] + pub calldata: Bytes, + pub symbol: String, +} +impl_wasm_traits!(ExtendedApprovalCalldata); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[serde(rename_all = "camelCase")] +pub struct ExternalCall { + #[tsify(type = "string")] + pub to: Address, + #[tsify(type = "string")] + pub calldata: Bytes, +} +impl_wasm_traits!(ExternalCall); + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[serde(rename_all = "camelCase")] +pub struct DeploymentTransactionArgs { + pub approvals: Vec, + #[tsify(type = "string")] + pub deployment_calldata: Bytes, + #[tsify(type = "string")] + pub orderbook_address: Address, + pub chain_id: u32, + pub emit_meta_call: Option, +} +impl_wasm_traits!(DeploymentTransactionArgs); + +#[wasm_export] +impl RaindexOrderBuilder { + #[wasm_export( + js_name = "checkAllowances", + unchecked_return_type = "AllowancesResult", + return_description = "Token allowances for the deployment" + )] + pub async fn check_allowances( + &mut self, + #[wasm_export(param_description = "Owner wallet address")] owner: String, + ) -> Result { + Ok(self.inner.check_allowances(owner).await?) + } + + #[wasm_export( + js_name = "generateApprovalCalldatas", + unchecked_return_type = "ApprovalCalldataResult", + return_description = "Approval transaction calldatas or NoDeposits" + )] + pub async fn generate_approval_calldatas( + &mut self, + #[wasm_export(param_description = "Owner wallet address")] owner: String, + ) -> Result { + Ok(self.inner.generate_approval_calldatas(owner).await?) + } + + #[wasm_export( + js_name = "generateDepositCalldatas", + unchecked_return_type = "DepositCalldataResult", + return_description = "Deposit transaction calldatas or NoDeposits" + )] + pub async fn generate_deposit_calldatas( + &mut self, + ) -> Result { + Ok(self.inner.generate_deposit_calldatas().await?) + } + + #[wasm_export( + js_name = "generateAddOrderCalldata", + unchecked_return_type = "AddOrderCalldataResult", + return_description = "Add order transaction calldata" + )] + pub async fn generate_add_order_calldata( + &mut self, + ) -> Result { + Ok(self.inner.generate_add_order_calldata().await?) + } + + #[wasm_export( + js_name = "generateDepositAndAddOrderCalldatas", + unchecked_return_type = "DepositAndAddOrderCalldataResult", + return_description = "Combined deposit and add order multicall calldata" + )] + pub async fn generate_deposit_and_add_order_calldatas( + &mut self, + ) -> Result { + Ok(self + .inner + .generate_deposit_and_add_order_calldatas() + .await?) + } + + #[wasm_export(js_name = "setVaultId", unchecked_return_type = "void")] + pub fn set_vault_id( + &mut self, + #[wasm_export(param_description = "Vault type (input or output)")] r#type: VaultType, + #[wasm_export(param_description = "Token key")] token: String, + #[wasm_export(param_description = "Optional vault ID as hex string")] vault_id: Option< + String, + >, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.set_vault_id(r#type, token, vault_id)?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export( + js_name = "getVaultIds", + unchecked_return_type = "IOVaultIds", + return_description = "Map of input/output vault IDs by token" + )] + pub fn get_vault_ids(&self) -> Result { + Ok(self.inner.get_vault_ids()?) + } + + #[wasm_export( + js_name = "hasAnyVaultId", + unchecked_return_type = "boolean", + return_description = "Whether any vault ID has been set" + )] + pub fn has_any_vault_id(&self) -> Result { + Ok(self.inner.has_any_vault_id()?) + } + + #[wasm_export(js_name = "updateScenarioBindings", unchecked_return_type = "void")] + pub fn update_scenario_bindings(&mut self) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.update_scenario_bindings()?; + Ok(()) + } + + #[wasm_export( + js_name = "getDeploymentTransactionArgs", + unchecked_return_type = "DeploymentTransactionArgs", + return_description = "Complete deployment transaction arguments" + )] + pub async fn get_deployment_transaction_args( + &mut self, + #[wasm_export(param_description = "Owner wallet address")] owner: String, + ) -> Result { + Ok(self.inner.get_deployment_transaction_args(owner).await?) + } +} diff --git a/crates/js_api/src/raindex_order_builder/select_tokens.rs b/crates/js_api/src/raindex_order_builder/select_tokens.rs new file mode 100644 index 0000000000..35593eb54a --- /dev/null +++ b/crates/js_api/src/raindex_order_builder/select_tokens.rs @@ -0,0 +1,97 @@ +use super::*; + +#[wasm_export] +impl RaindexOrderBuilder { + #[wasm_export( + js_name = "getSelectTokens", + unchecked_return_type = "OrderBuilderSelectTokensCfg[]", + return_description = "Array of token selection configurations" + )] + pub fn get_select_tokens( + &self, + ) -> Result< + Vec, + RaindexOrderBuilderWasmError, + > { + Ok(self.inner.get_select_tokens()?) + } + + #[wasm_export( + js_name = "isSelectTokenSet", + unchecked_return_type = "boolean", + return_description = "Whether the token has been selected" + )] + pub fn is_select_token_set( + &self, + #[wasm_export(param_description = "Token key to check")] key: String, + ) -> Result { + Ok(self.inner.is_select_token_set(key)?) + } + + #[wasm_export(js_name = "checkSelectTokens", unchecked_return_type = "void")] + pub fn check_select_tokens(&self) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.check_select_tokens()?; + Ok(()) + } + + #[wasm_export(js_name = "setSelectToken", unchecked_return_type = "void")] + pub async fn set_select_token( + &mut self, + #[wasm_export(param_description = "Token key from select-tokens configuration")] + key: String, + #[wasm_export(param_description = "Token contract address as hex string")] address: String, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.set_select_token(key, address).await?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export(js_name = "unsetSelectToken", unchecked_return_type = "void")] + pub fn unset_select_token( + &mut self, + #[wasm_export(param_description = "Token key to deselect")] key: String, + ) -> Result<(), RaindexOrderBuilderWasmError> { + self.inner.unset_select_token(key)?; + self.execute_state_update_callback()?; + Ok(()) + } + + #[wasm_export( + js_name = "areAllTokensSelected", + unchecked_return_type = "boolean", + return_description = "Whether all required tokens have been selected" + )] + pub fn are_all_tokens_selected(&self) -> Result { + Ok(self.inner.are_all_tokens_selected()?) + } + + #[wasm_export( + js_name = "getAllTokens", + unchecked_return_type = "ExtendedTokenInfo[]", + return_description = "Array of token information for the deployment's network" + )] + pub async fn get_all_tokens( + &self, + #[wasm_export(param_description = "Optional search string to filter tokens")] + search: Option, + ) -> Result, RaindexOrderBuilderWasmError> { + Ok(self.inner.get_all_tokens(search).await?) + } + + #[wasm_export( + js_name = "getAccountBalance", + unchecked_return_type = "AccountBalance", + return_description = "Token balance for the specified account", + preserve_js_class + )] + pub async fn get_account_balance( + &self, + #[wasm_export(param_description = "Token contract address")] token_address: String, + #[wasm_export(param_description = "Account address to check balance for")] owner: String, + ) -> Result< + rain_orderbook_common::raindex_client::vaults::AccountBalance, + RaindexOrderBuilderWasmError, + > { + Ok(self.inner.get_account_balance(token_address, owner).await?) + } +} diff --git a/crates/js_api/src/raindex_order_builder/state_management.rs b/crates/js_api/src/raindex_order_builder/state_management.rs new file mode 100644 index 0000000000..fb9a56684f --- /dev/null +++ b/crates/js_api/src/raindex_order_builder/state_management.rs @@ -0,0 +1,113 @@ +use super::*; +use rain_orderbook_common::raindex_order_builder::state_management as inner_sm; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)] +#[serde(rename_all = "camelCase")] +pub struct AllBuilderConfig { + pub field_definitions_without_defaults: Vec, + pub field_definitions_with_defaults: Vec, + pub deposits: Vec, + pub order_inputs: Vec, + pub order_outputs: Vec, +} +impl_wasm_traits!(AllBuilderConfig); + +#[wasm_export] +impl RaindexOrderBuilder { + #[wasm_export( + js_name = "executeStateUpdateCallback", + unchecked_return_type = "void", + return_description = "Callback executed successfully or no callback registered" + )] + pub fn execute_state_update_callback(&self) -> Result<(), RaindexOrderBuilderWasmError> { + if let Some(callback) = &self.state_update_callback { + let serialized = self.inner.serialize_state()?; + let js_value = JsValue::from_str(&serialized); + callback.call1(&JsValue::NULL, &js_value).map_err(|e| { + RaindexOrderBuilderWasmError::JsError( + e.as_string().unwrap_or("callback error".to_string()), + ) + })?; + } + Ok(()) + } +} + +#[wasm_export] +impl RaindexOrderBuilder { + #[wasm_export( + js_name = "serializeState", + unchecked_return_type = "string", + return_description = "Base64-encoded compressed serialized state" + )] + pub fn serialize_state(&self) -> Result { + Ok(self.inner.serialize_state()?) + } + + #[wasm_export( + js_name = "newFromState", + preserve_js_class, + return_description = "Restored builder instance" + )] + pub async fn new_from_state( + #[wasm_export(param_description = "Complete dotrain YAML content")] dotrain: String, + #[wasm_export(param_description = "Optional additional YAML settings")] settings: Option< + Vec, + >, + #[wasm_export(param_description = "Serialized state string from serializeState")] + serialized: String, + #[wasm_export(param_description = "Optional state update callback function")] + state_update_callback: Option, + ) -> Result { + let inner = RaindexOrderBuilderInner::new_from_state(dotrain, settings, serialized).await?; + Ok(RaindexOrderBuilder { + inner, + state_update_callback, + }) + } + + #[wasm_export( + js_name = "getAllBuilderConfig", + unchecked_return_type = "AllBuilderConfig", + return_description = "Complete builder configuration for all fields, deposits, and I/O" + )] + pub fn get_all_builder_config( + &self, + ) -> Result { + Ok(self.inner.get_all_builder_config()?) + } + + #[wasm_export( + js_name = "computeStateHash", + unchecked_return_type = "string", + return_description = "Base64-encoded SHA256 hash of the dotrain content" + )] + pub async fn compute_state_hash( + #[wasm_export(param_description = "Complete dotrain YAML content")] dotrain: String, + #[wasm_export(param_description = "Optional additional YAML settings")] settings: Option< + Vec, + >, + ) -> Result { + let dotrain_order = + rain_orderbook_common::dotrain_order::DotrainOrder::create(dotrain, settings) + .await + .map_err(RaindexOrderBuilderError::from)?; + Ok(RaindexOrderBuilderInner::compute_state_hash( + &dotrain_order, + )?) + } + + #[wasm_export( + js_name = "generateDotrainBuilderStateInstanceV1", + unchecked_return_type = "OrderBuilderStateV1", + return_description = "Builder state instance for metadata embedding" + )] + pub fn generate_dotrain_builder_state_instance_v1( + &self, + ) -> Result< + rain_metadata::types::dotrain::order_builder_state_v1::OrderBuilderStateV1, + RaindexOrderBuilderWasmError, + > { + Ok(self.inner.generate_dotrain_builder_state_instance_v1()?) + } +} diff --git a/crates/js_api/src/registry.rs b/crates/js_api/src/registry.rs index dfb0046a1a..8519e728a8 100644 --- a/crates/js_api/src/registry.rs +++ b/crates/js_api/src/registry.rs @@ -1,6 +1,6 @@ -use crate::gui::{DotrainOrderGui, GuiError}; +use crate::raindex_order_builder::{RaindexOrderBuilder, RaindexOrderBuilderWasmError}; use crate::yaml::{OrderbookYaml, OrderbookYamlError}; -use rain_orderbook_app_settings::gui::NameAndDescriptionCfg; +use rain_orderbook_app_settings::order_builder::NameAndDescriptionCfg; use rain_orderbook_common::raindex_client::{RaindexClient, RaindexError as RaindexClientError}; use reqwest; use serde::{Deserialize, Serialize}; @@ -44,7 +44,7 @@ use wasm_bindgen_utils::{impl_wasm_traits, prelude::*, wasm_export}; /// 1. **Registry Creation** → Fetches and parses registry file /// 2. **List Orders** → Get available order strategies with metadata /// 3. **List Deployments** → Get deployment options for selected order -/// 4. **Create GUI** → Instantiate DotrainOrderGui with merged content +/// 4. **Create Builder** → Instantiate RaindexOrderBuilder with merged content /// /// ## Examples /// @@ -58,8 +58,8 @@ use wasm_bindgen_utils::{impl_wasm_traits, prelude::*, wasm_export}; /// // Get deployments for specific order /// const deployments = await registry.getDeploymentDetails("fixed-limit"); /// -/// // Create GUI instance -/// const gui = await registry.getGui("fixed-limit", "mainnet", stateCallback); +/// // Create order builder instance +/// const builder = await registry.getOrderBuilder("fixed-limit", "mainnet", stateCallback); /// ``` #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[wasm_bindgen] @@ -111,7 +111,7 @@ pub struct DotrainRegistry { /// - **Value**: Raw dotrain content fetched from the corresponding URL /// /// This content is fetched in parallel during registry initialization and stored - /// for quick access. It gets merged with `settings` content when creating GUIs. + /// for quick access. It gets merged with `settings` content when creating builders. orders: HashMap, } @@ -145,7 +145,7 @@ pub enum DotrainRegistryError { #[error("Invalid URL: {0}")] UrlParseError(#[from] url::ParseError), #[error(transparent)] - GuiError(#[from] GuiError), + BuilderError(#[from] RaindexOrderBuilderWasmError), #[error(transparent)] OrderbookYamlError(#[from] OrderbookYamlError), #[error(transparent)] @@ -179,7 +179,7 @@ impl DotrainRegistryError { DotrainRegistryError::UrlParseError(err) => { format!("Invalid URL format: {}. Please ensure the URL is properly formatted.", err) } - DotrainRegistryError::GuiError(err) => err.to_readable_msg(), + DotrainRegistryError::BuilderError(err) => err.to_readable_msg(), DotrainRegistryError::OrderbookYamlError(err) => err.to_readable_msg(), DotrainRegistryError::RaindexClientError(err) => err.to_readable_msg(), } @@ -327,7 +327,7 @@ impl DotrainRegistry { let settings = self.settings_sources(); for (order_key, dotrain) in &self.orders { - match DotrainOrderGui::get_order_details(dotrain.clone(), settings.clone()) { + match RaindexOrderBuilder::get_order_details(dotrain.clone(), settings.clone()) { Ok(details) => { valid.insert(order_key.clone(), details); } @@ -408,30 +408,30 @@ impl DotrainRegistry { .ok_or(DotrainRegistryError::OrderKeyNotFound(order_key.clone()))?; let settings = self.settings_sources(); let deployment_details = - DotrainOrderGui::get_deployment_details(dotrain.clone(), settings.clone())?; + RaindexOrderBuilder::get_deployment_details(dotrain.clone(), settings.clone())?; Ok(deployment_details) } - /// Creates a DotrainOrderGui instance for a specific order and deployment. + /// Creates a RaindexOrderBuilder instance for a specific order and deployment. /// - /// This is a convenience method that combines getting a DotrainOrder and creating a GUI. + /// This is a convenience method that combines getting a DotrainOrder and creating a builder. /// /// ## Examples /// /// ```javascript /// // Simple usage without state callback - /// const result = await registry.getGui("fixed-limit", "mainnet-deployment"); + /// const result = await registry.getOrderBuilder("fixed-limit", "mainnet-deployment"); /// if (result.error) { - /// console.error("Failed to create GUI:", result.error.readableMsg); + /// console.error("Failed to create order builder:", result.error.readableMsg); /// return; /// } - /// const gui = result.value; + /// const builder = result.value; /// /// // Usage with state update callback for auto-saving /// const stateCallback = (newState) => { - /// localStorage.setItem('gui-state', JSON.stringify(newState)); + /// localStorage.setItem('builder-state', JSON.stringify(newState)); /// }; - /// const resultWithCallback = await registry.getGui( + /// const resultWithCallback = await registry.getOrderBuilder( /// "fixed-limit", /// "mainnet-deployment", /// undefined, @@ -439,8 +439,8 @@ impl DotrainRegistry { /// ); /// /// // Usage restoring from serialized state (with optional callback) - /// const savedState = localStorage.getItem('gui-state'); - /// const resultFromState = await registry.getGui( + /// const savedState = localStorage.getItem('builder-state'); + /// const resultFromState = await registry.getOrderBuilder( /// "fixed-limit", /// "mainnet-deployment", /// savedState, @@ -448,45 +448,45 @@ impl DotrainRegistry { /// ); /// ``` #[wasm_export( - js_name = "getGui", + js_name = "getOrderBuilder", preserve_js_class, - unchecked_return_type = "DotrainOrderGui", - return_description = "DotrainOrderGui instance for the specified order and deployment" + unchecked_return_type = "RaindexOrderBuilder", + return_description = "RaindexOrderBuilder instance for the specified order and deployment" )] - pub async fn get_gui( + pub async fn get_order_builder( &self, #[wasm_export( js_name = "orderKey", - param_description = "Order key to fetch the GUI for" + param_description = "Order key to fetch the order builder for" )] order_key: String, #[wasm_export( js_name = "deploymentKey", - param_description = "Deployment key to create the GUI for" + param_description = "Deployment key to create the order builder for" )] deployment_key: String, #[wasm_export( js_name = "serializedState", - param_description = "Optional serialized GUI state string used to restore form progress before falling back to deployment defaults" + param_description = "Optional serialized builder state string used to restore form progress before falling back to deployment defaults" )] serialized_state: Option, #[wasm_export( js_name = "stateUpdateCallback", 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." + This is useful for auto-saving the state of the builder across sessions." )] state_update_callback: Option, - ) -> Result { + ) -> Result { let dotrain = self .orders .get(&order_key) .ok_or(DotrainRegistryError::OrderKeyNotFound(order_key.clone()))?; let settings = self.settings_sources(); - let gui_result = match serialized_state { + let result = match serialized_state { Some(serialized_state) => { - match DotrainOrderGui::new_from_state( + match RaindexOrderBuilder::new_from_state( dotrain.clone(), settings.clone(), serialized_state, @@ -494,9 +494,9 @@ impl DotrainRegistry { ) .await { - Ok(gui) => Ok(gui), + Ok(builder) => Ok(builder), Err(_) => { - DotrainOrderGui::new_with_deployment( + RaindexOrderBuilder::new_with_deployment( dotrain.clone(), settings.clone(), deployment_key, @@ -507,7 +507,7 @@ impl DotrainRegistry { } } None => { - DotrainOrderGui::new_with_deployment( + RaindexOrderBuilder::new_with_deployment( dotrain.clone(), settings.clone(), deployment_key, @@ -517,8 +517,8 @@ impl DotrainRegistry { } }; - let gui = gui_result.map_err(DotrainRegistryError::GuiError)?; - Ok(gui) + let builder = result.map_err(DotrainRegistryError::BuilderError)?; + Ok(builder) } /// Creates an OrderbookYaml instance from the registry's shared settings. @@ -799,13 +799,13 @@ tokens: fn mock_dotrain_prefix() -> String { format!( r#"version: {version} -gui:"#, +builder:"#, version = SpecVersion::current() ) } const MOCK_DOTRAIN_BODY: &str = r#" - name: Test gui + name: Test builder description: Test description short-description: Test short description deployments: @@ -1005,7 +1005,7 @@ _ _: 1 1; assert!(order_details.valid.contains_key("auction-dca")); let fixed_limit_details = order_details.valid.get("fixed-limit").unwrap(); - assert_eq!(fixed_limit_details.name, "Test gui"); + assert_eq!(fixed_limit_details.name, "Test builder"); assert_eq!(fixed_limit_details.description, "Test description"); } @@ -1438,7 +1438,7 @@ _ _: 1 1; } #[tokio::test] - async fn test_get_gui() { + async fn test_get_order_builder() { let server = MockServer::start_async().await; let test_registry_content = format!( @@ -1477,37 +1477,37 @@ _ _: 1 1; assert!(registry.order_urls.contains_key("second-order")); assert_eq!(registry.orders.len(), 2); - let gui1 = registry - .get_gui("first-order".to_string(), "flare".to_string(), None, None) + let mut builder1 = registry + .get_order_builder("first-order".to_string(), "flare".to_string(), None, None) .await .unwrap(); - let deployment_details1 = gui1.get_current_deployment().unwrap(); + let deployment_details1 = builder1.get_current_deployment().unwrap(); assert_eq!(deployment_details1.name, "Flare order name"); assert_eq!(deployment_details1.description, "Flare order description"); - let default_serialized_state = gui1.serialize_state().unwrap(); + let default_serialized_state = builder1.serialize_state().unwrap(); - let gui2 = registry - .get_gui("second-order".to_string(), "base".to_string(), None, None) + let builder2 = registry + .get_order_builder("second-order".to_string(), "base".to_string(), None, None) .await .unwrap(); - let deployment_details2 = gui2.get_current_deployment().unwrap(); + let deployment_details2 = builder2.get_current_deployment().unwrap(); assert_eq!(deployment_details2.name, "Base order name"); assert_eq!(deployment_details2.description, "Base order description"); - let mut gui_with_state = registry - .get_gui("first-order".to_string(), "flare".to_string(), None, None) + let mut builder_with_state = registry + .get_order_builder("first-order".to_string(), "flare".to_string(), None, None) .await .unwrap(); - gui_with_state + builder_with_state .set_field_value("test-binding".to_string(), "42".to_string()) .unwrap(); - let saved_state = gui_with_state.serialize_state().unwrap(); + let saved_state = builder_with_state.serialize_state().unwrap(); - let restored_gui = registry - .get_gui( + let restored_builder = registry + .get_order_builder( "first-order".to_string(), "flare".to_string(), Some(saved_state.clone()), @@ -1515,10 +1515,10 @@ _ _: 1 1; ) .await .unwrap(); - assert_eq!(restored_gui.serialize_state().unwrap(), saved_state); + assert_eq!(restored_builder.serialize_state().unwrap(), saved_state); - let fallback_gui = registry - .get_gui( + let fallback_builder = registry + .get_order_builder( "first-order".to_string(), "flare".to_string(), Some("not-a-valid-state".to_string()), @@ -1527,12 +1527,12 @@ _ _: 1 1; .await .unwrap(); assert_eq!( - fallback_gui.serialize_state().unwrap(), + fallback_builder.serialize_state().unwrap(), default_serialized_state ); let result = registry - .get_gui( + .get_order_builder( "non-existent-order".to_string(), "flare".to_string(), None, @@ -1587,7 +1587,7 @@ registrys: ) } - const MOCK_DOTRAIN_SIMPLE: &str = r#"gui: + const MOCK_DOTRAIN_SIMPLE: &str = r#"builder: name: Test Order description: Test description deployments: diff --git a/crates/settings/ARCHITECTURE.md b/crates/settings/ARCHITECTURE.md index b219ee9a7e..4b44712135 100644 --- a/crates/settings/ARCHITECTURE.md +++ b/crates/settings/ARCHITECTURE.md @@ -5,10 +5,10 @@ This crate defines the configuration model, parsing, validation and update utili At a glance: - Input format: one or more YAML documents (via `StrictYaml` from `strict_yaml_rust`). -- Output types: `NetworkCfg`, `TokenCfg`, `OrderbookCfg`, `SubgraphCfg`, `DeployerCfg`, `OrderCfg`, `ScenarioCfg`, `DeploymentCfg`, `GuiCfg`, `ChartCfg`, `MetaboardCfg`, `AccountCfg`, plus helpers. +- Output types: `NetworkCfg`, `TokenCfg`, `OrderbookCfg`, `SubgraphCfg`, `DeployerCfg`, `OrderCfg`, `ScenarioCfg`, `DeploymentCfg`, `OrderBuilderCfg`, `ChartCfg`, `MetaboardCfg`, `AccountCfg`, plus helpers. - Cross‑document merge: parse operations accept a vector of YAML documents and merge sections across them, rejecting duplicate keys deterministically. - Remote sources: optional “using‑*” sections enable fetching networks/tokens from external endpoints and merging them into the local model. -- Context: a runtime context carries selected deployment/order, token selection for GUI flows, remote caches, and supports string interpolation from order paths. +- Context: a runtime context carries selected deployment/order, token selection for order builder flows, remote caches, and supports string interpolation from order paths. - WASM/TypeScript: many types derive `Tsify` and implement WASM trait helpers for interop with the webapp. @@ -22,13 +22,13 @@ Core traits and helpers live under `src/yaml` and are used by all config types: - `YamlParsableHash`: for map‑shaped sections (e.g. `networks`, `tokens`, `orders`). Provides `parse_all_from_yaml` and `parse_from_yaml(key)`. - `YamlParsableVector`: for vector‑shaped items if needed. - `YamlParsableString`: for single string fields with optionality (e.g. `SpecVersion`, `Sentry`). - - `YamlParseableValue`: for single logical objects that are not a map (e.g. `GuiCfg`, `RemoteTokensCfg`). + - `YamlParseableValue`: for single logical objects that are not a map (e.g. `OrderBuilderCfg`, `RemoteTokensCfg`). - Context and caching - `Context` holds: - `order: Option>` – the current order for interpolation. - - `select_tokens: Option>` – allow GUI to reference tokens by key without YAML definitions. - - `gui_context`: current deployment/order selection. + - `select_tokens: Option>` – allow order builder to reference tokens by key without YAML definitions. + - `builder_context`: current deployment/order selection. - `yaml_cache`: remote networks/tokens cache injected by providers. - Interpolation: `Context::interpolate("... ${order.inputs.0.token.symbol} ...")` resolves values from the current order (inputs/outputs, token address/symbol/label/decimals, vault IDs). - Path resolution helpers and errors: `ContextError::{NoOrder, InvalidPath, InvalidIndex, PropertyNotFound}` with human‑readable messages. @@ -68,10 +68,10 @@ Two top‑level providers wrap one or more YAML documents and expose a convenien - Also wraps `documents` and a `Cache`. - `new(sources, validation)` selectively validates orders, scenarios, deployments. - Accessors: - - Orders: `get_order_keys`, `get_orders`, `get_order(key)`, `get_order_for_gui_deployment(order_key, deployment_key)`. + - Orders: `get_order_keys`, `get_orders`, `get_order(key)`, `get_order_for_builder_deployment(order_key, deployment_key)`. - Scenarios: `get_scenario_keys`, `get_scenarios`, `get_scenario(key)`. - Deployments: `get_deployment_keys`, `get_deployments`, `get_deployment(key)`. - - GUI: `get_gui(current_deployment)` parses optional GUI section with deployment‑scoped overrides and select‑tokens. + - Order Builder: `get_order_builder(current_deployment)` parses optional builder section with deployment‑scoped overrides and select‑tokens. - Charts: `get_chart_keys`, `get_charts`, `get_chart(key)`. - Serde mirrors `OrderbookYaml`. @@ -163,7 +163,7 @@ These three model how orders are defined, how they are executed (bindings, block - `OrderCfg { key, inputs: Vec, outputs: Vec, network: Arc, deployer?: Arc, orderbook?: Arc }`. - `OrderIOCfg { token_key: String, token?: Arc, vault_id?: U256 }` – `token_key` preserves the declared token name even when the token is unresolved for select‑tokens; vault IDs are arbitrary U256 strings. - Validation and network unification - - Inputs/outputs must each contain `token` (unless permitted by GUI select‑tokens through context) and optional `vault-id`. + - Inputs/outputs must each contain `token` (unless permitted by builder select‑tokens through context) and optional `vault-id`. - The order’s effective `network` is inferred from first matching component (deployer/orderbook/token), and all references must match. Mismatch yields detailed errors (`DeployerNetworkDoesNotMatch`, `OrderbookNetworkDoesNotMatch`, `InputTokenNetworkDoesNotMatch`, `OutputTokenNetworkDoesNotMatch`). If no network can be determined, `NetworkNotFoundError` is raised. - Vault IDs are validated via `U256::from_str`. - Mutations @@ -199,21 +199,21 @@ These three model how orders are defined, how they are executed (bindings, block - `DeploymentCfg { key, scenario: Arc, order: Arc }`. - Parsing - - Respects optional GUI context for “current deployment” to allow per‑deployment scoping when documents contain many deployments. + - Respects optional order builder context for “current deployment” to allow per‑deployment scoping when documents contain many deployments. - Ensures the selected order and scenario share the same deployer; otherwise returns `ParseDeploymentConfigSourceError::NoMatch`. - Helper `parse_order_key(docs, deployment_key)` extracts the order name for a deployment. -## GUI Configuration (`gui.rs`) +## Order Builder Configuration (`order_builder.rs`) -The GUI DSL configures per‑deployment user inputs (fields), deposit presets/validation, and “select tokens” behavior for orders rendered in the UI. +The order builder DSL configures per‑deployment user inputs (fields), deposit presets/validation, and “select tokens” behavior for orders rendered in the UI. - Source types (pure data) to transform into runtime types: - - `GuiConfigSourceCfg { name, description, deployments: Map }`. - - `GuiDeploymentSourceCfg { name, description, deposits: [GuiDepositSourceCfg], fields: [GuiFieldDefinitionSourceCfg], select_tokens?: [GuiSelectTokensCfg] }`. - - `GuiDepositSourceCfg { token: String, presets?: [String], validation?: DepositValidationCfg }`. - - `GuiFieldDefinitionSourceCfg { binding, name, description?, presets?: [GuiPresetSourceCfg], default?, show_custom_field?, validation? }`. - - `GuiPresetSourceCfg { name?, value }`. + - `OrderBuilderConfigSourceCfg { name, description, deployments: Map }`. + - `OrderBuilderDeploymentSourceCfg { name, description, deposits: [OrderBuilderDepositSourceCfg], fields: [OrderBuilderFieldDefinitionSourceCfg], select_tokens?: [OrderBuilderSelectTokensCfg] }`. + - `OrderBuilderDepositSourceCfg { token: String, presets?: [String], validation?: DepositValidationCfg }`. + - `OrderBuilderFieldDefinitionSourceCfg { binding, name, description?, presets?: [OrderBuilderPresetSourceCfg], default?, show_custom_field?, validation? }`. + - `OrderBuilderPresetSourceCfg { name?, value }`. - Validation enums: - `FieldValueValidationCfg::Number { minimum?, exclusive_minimum?, maximum?, exclusive_maximum? }`. - `FieldValueValidationCfg::String { min_length?, max_length? }`. @@ -221,15 +221,15 @@ The GUI DSL configures per‑deployment user inputs (fields), deposit presets/va - `DepositValidationCfg { minimum?, exclusive_minimum?, maximum?, exclusive_maximum? }`. - Runtime types (used by app/wasm): - - `GuiCfg { name, description, deployments: Map }`. - - `GuiDeploymentCfg { key, deployment: Arc, name, description, deposits: [GuiDepositCfg], fields: [GuiFieldDefinitionCfg], select_tokens? }`. - - `GuiDepositCfg { token?: Arc, presets?, validation? }`. - - `GuiFieldDefinitionCfg { binding, name, description?, presets?: [GuiPresetCfg], default?, show_custom_field?, validation? }`. - - `GuiPresetCfg { id, name?, value }`. + - `OrderBuilderCfg { name, description, deployments: Map }`. + - `OrderBuilderDeploymentCfg { key, deployment: Arc, name, description, deposits: [OrderBuilderDepositCfg], fields: [OrderBuilderFieldDefinitionCfg], select_tokens? }`. + - `OrderBuilderDepositCfg { token?: Arc, presets?, validation? }`. + - `OrderBuilderFieldDefinitionCfg { binding, name, description?, presets?: [OrderBuilderPresetCfg], default?, show_custom_field?, validation? }`. + - `OrderBuilderPresetCfg { id, name?, value }`. - Parsing - - `GuiCfg::parse_from_yaml_optional(documents, context)` traverses a `gui:` map, applying deployment scoping from GUI context and order/deployment context, and builds a `GuiCfg` if present. - - Helper queries: `check_gui_key_exists`, `parse_deployment_keys`, `parse_order_details`, `parse_deployment_details`, `parse_field_presets`, `parse_select_tokens`. + - `OrderBuilderCfg::parse_from_yaml_optional(documents, context)` traverses a `builder:` map, applying deployment scoping from order builder context and order/deployment context, and builds an `OrderBuilderCfg` if present. + - Helper queries: `check_builder_key_exists`, `parse_deployment_keys`, `parse_order_details`, `parse_deployment_details`, `parse_field_presets`, `parse_select_tokens`. - Integrates tokens (if present) to resolve deposit token references to `Arc`. With “select‑tokens”, the context is seeded with allowed token keys so orders may omit YAML token entries. @@ -302,7 +302,7 @@ For map‑shaped sections parsed across multiple documents, the crate enforces u ## Error Reporting Philosophy -All parser and validator errors convert to `YamlError` or module‑specific `*Parse*Error` enums with a `to_readable_msg()` that is safe to show to end users. `FieldErrorKind::{Missing, InvalidType, InvalidValue}` always include a precise human‑readable location (e.g., "order 'MyOrder'", "output index '0' in order 'MyOrder'", "gui deployment 'MyDeployment'"). +All parser and validator errors convert to `YamlError` or module‑specific `*Parse*Error` enums with a `to_readable_msg()` that is safe to show to end users. `FieldErrorKind::{Missing, InvalidType, InvalidValue}` always include a precise human‑readable location (e.g., "order 'MyOrder'", "output index '0' in order 'MyOrder'", "order builder deployment 'MyDeployment'"). ## WASM/TypeScript Interop @@ -320,7 +320,7 @@ When building for `wasm32`, many types derive `Tsify` and implement WASM trait h - Validate dotrain YAML and build a deployment plan: 1. Load strings into `DotrainYaml::new([...], DotrainYamlValidation::full())`. 2. Resolve `OrderCfg`, `ScenarioCfg`, and `DeploymentCfg`; ensure network/deployer invariants hold. - 3. Optional GUI: parse `GuiCfg` scoped to a specific deployment and seed context with `select-tokens`. + 3. Optional Order Builder: parse `OrderBuilderCfg` scoped to a specific deployment and seed context with `select-tokens`. 4. Optional Charts: parse `ChartCfg` for visualization. @@ -340,16 +340,16 @@ When building for `wasm32`, many types derive `Tsify` and implement WASM trait h - `orders: { key: { inputs: [{ token, vault-id? }, ...], outputs: [...], deployer?, orderbook? } }` - `scenarios: { key: { bindings: {k:v}, runs?, blocks?, deployer?, scenarios?: {...} } }` - `deployments: { key: { scenario, order } }` - - `gui: { name, description, deployments: { key: { name, description, deposits: [...], fields: [...], select-tokens?: [...] } } }` + - `builder: { name, description, deployments: { key: { name, description, deposits: [...], fields: [...], select-tokens?: [...] } } }` - `charts: { key: { scenario?, plots?: {...}, metrics?: [...] } }` - `sentry: true|false|1|0` ## Testing -The crate ships extensive unit tests for every parser and update path, including error paths with precise messages. Test helpers in `src/test.rs` construct mock networks/deployers/tokens/orderbooks; parser modules provide happy‑path and negative test cases (duplicate keys, missing/invalid fields, range validation for blocks, GUI validation, remote fetch flows with http mocks, etc.). +The crate ships extensive unit tests for every parser and update path, including error paths with precise messages. Test helpers in `src/test.rs` construct mock networks/deployers/tokens/orderbooks; parser modules provide happy‑path and negative test cases (duplicate keys, missing/invalid fields, range validation for blocks, builder validation, remote fetch flows with http mocks, etc.). ## Summary -The settings crate provides a single, well‑typed interface over YAML configuration for the Rain Orderbook ecosystem: robust parsing across multiple files, strict validation with user‑friendly errors, safe in‑place updates, optional remote augmentation, contextual interpolation, GUI and chart DSLs, and WASM interop. Other crates consume these types to build CLIs, runtimes, and UIs without re‑implementing YAML logic. +The settings crate provides a single, well‑typed interface over YAML configuration for the Rain Orderbook ecosystem: robust parsing across multiple files, strict validation with user‑friendly errors, safe in‑place updates, optional remote augmentation, contextual interpolation, builder and chart DSLs, and WASM interop. Other crates consume these types to build CLIs, runtimes, and UIs without re‑implementing YAML logic. diff --git a/crates/settings/src/deployment.rs b/crates/settings/src/deployment.rs index e071c9003b..cd86f3ccf4 100644 --- a/crates/settings/src/deployment.rs +++ b/crates/settings/src/deployment.rs @@ -9,7 +9,7 @@ use thiserror::Error; #[cfg(target_family = "wasm")] use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; use yaml::{ - context::{Context, GuiContextTrait}, + context::{Context, OrderBuilderContextTrait}, default_document, require_hash, require_string, FieldErrorKind, YamlError, YamlParsableHash, }; diff --git a/crates/settings/src/lib.rs b/crates/settings/src/lib.rs index 053c8d05ae..72aa13a34f 100644 --- a/crates/settings/src/lib.rs +++ b/crates/settings/src/lib.rs @@ -2,13 +2,13 @@ pub mod accounts; pub mod blocks; pub mod chart; pub mod deployment; -pub mod gui; pub mod local_db_manifest; pub mod local_db_remotes; pub mod local_db_sync; pub mod metaboard; pub mod network; pub mod order; +pub mod order_builder; pub mod orderbook; pub mod plot_source; pub mod rainlang; @@ -26,10 +26,10 @@ pub mod yaml; pub(crate) use chart::*; pub(crate) use deployment::*; -pub(crate) use gui::*; pub(crate) use local_db_sync::*; pub(crate) use network::*; pub(crate) use order::*; +pub(crate) use order_builder::*; pub(crate) use orderbook::*; pub(crate) use plot_source::*; pub(crate) use rainlang::*; diff --git a/crates/settings/src/order.rs b/crates/settings/src/order.rs index 2debcdc43d..25aeea9485 100644 --- a/crates/settings/src/order.rs +++ b/crates/settings/src/order.rs @@ -13,7 +13,7 @@ const ALLOWED_ORDER_KEYS: [&str; 5] = ["inputs", "oracle-url", "orderbook", "out const ALLOWED_ORDER_IO_KEYS: [&str; 2] = ["token", "vault-id"]; use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; use yaml::{ - context::{Context, GuiContextTrait, SelectTokensContext}, + context::{Context, OrderBuilderContextTrait, SelectTokensContext}, default_document, optional_string, require_hash, require_string, require_vec, YamlError, YamlParsableHash, }; diff --git a/crates/settings/src/gui.rs b/crates/settings/src/order_builder.rs similarity index 80% rename from crates/settings/src/gui.rs rename to crates/settings/src/order_builder.rs index b5c2b83c42..04bd367695 100644 --- a/crates/settings/src/gui.rs +++ b/crates/settings/src/order_builder.rs @@ -1,6 +1,6 @@ use crate::{ yaml::{ - context::{Context, GuiContextTrait}, + context::{Context, OrderBuilderContextTrait}, default_document, get_hash_value, get_hash_value_as_option, optional_hash, optional_string, optional_vec, require_string, require_vec, FieldErrorKind, YamlError, YamlParsableHash, YamlParseableValue, @@ -15,8 +15,9 @@ use std::{ }; use strict_yaml_rust::{strict_yaml::Hash, StrictYaml}; -const ALLOWED_GUI_KEYS: [&str; 4] = ["name", "description", "short-description", "deployments"]; -const ALLOWED_GUI_DEPLOYMENT_KEYS: [&str; 6] = [ +const ALLOWED_ORDER_BUILDER_KEYS: [&str; 4] = + ["name", "description", "short-description", "deployments"]; +const ALLOWED_ORDER_BUILDER_DEPLOYMENT_KEYS: [&str; 6] = [ "name", "description", "short-description", @@ -70,22 +71,22 @@ pub struct DepositValidationCfg { #[cfg(target_family = "wasm")] impl_wasm_traits!(DepositValidationCfg); -// Config source for Gui +// Config source for OrderBuilder #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] -pub struct GuiPresetSourceCfg { +pub struct OrderBuilderPresetSourceCfg { #[serde(skip_serializing_if = "Option::is_none")] pub name: Option, pub value: String, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(GuiPresetSourceCfg); +impl_wasm_traits!(OrderBuilderPresetSourceCfg); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] -pub struct GuiDepositSourceCfg { +pub struct OrderBuilderDepositSourceCfg { pub token: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub presets: Option>, @@ -96,13 +97,13 @@ pub struct GuiDepositSourceCfg { #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] -pub struct GuiFieldDefinitionSourceCfg { +pub struct OrderBuilderFieldDefinitionSourceCfg { pub binding: String, pub name: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub description: Option, #[cfg_attr(target_family = "wasm", tsify(optional))] - pub presets: Option>, + pub presets: Option>, #[cfg_attr(target_family = "wasm", tsify(optional))] pub default: Option, #[cfg_attr(target_family = "wasm", tsify(optional))] @@ -114,43 +115,43 @@ pub struct GuiFieldDefinitionSourceCfg { #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] -pub struct GuiDeploymentSourceCfg { +pub struct OrderBuilderDeploymentSourceCfg { pub name: String, pub description: String, pub short_description: Option, - pub deposits: Vec, - pub fields: Vec, + pub deposits: Vec, + pub fields: Vec, #[serde(skip_serializing_if = "Option::is_none")] - pub select_tokens: Option>, + pub select_tokens: Option>, } #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "kebab-case")] -pub struct GuiConfigSourceCfg { +pub struct OrderBuilderConfigSourceCfg { pub name: String, pub description: String, pub short_description: Option, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") + tsify(optional, type = "Record") )] - pub deployments: HashMap, + pub deployments: HashMap, } -impl GuiConfigSourceCfg { - pub fn try_into_gui( +impl OrderBuilderConfigSourceCfg { + pub fn try_into_order_builder( self, deployments: &HashMap>, tokens: &HashMap>, - ) -> Result { - let gui_deployments = self + ) -> Result { + let order_builder_deployments = self .deployments .iter() .map(|(deployment_name, deployment_source)| { let deployment = deployments .get(deployment_name) - .ok_or(ParseGuiConfigSourceError::DeploymentNotFoundError( + .ok_or(ParseOrderBuilderConfigSourceError::DeploymentNotFoundError( deployment_name.clone(), )) .map(Arc::clone)?; @@ -161,25 +162,25 @@ impl GuiConfigSourceCfg { .map(|deposit_source| { let token = tokens .get(&deposit_source.token) - .ok_or(ParseGuiConfigSourceError::TokenNotFoundError( + .ok_or(ParseOrderBuilderConfigSourceError::TokenNotFoundError( deposit_source.token.clone(), )) .map(Arc::clone)?; - Ok(GuiDepositCfg { + Ok(OrderBuilderDepositCfg { token_key: deposit_source.token.clone(), token: Some(token.clone()), presets: deposit_source.presets.clone(), validation: deposit_source.validation.clone(), }) }) - .collect::, ParseGuiConfigSourceError>>()?; + .collect::, ParseOrderBuilderConfigSourceError>>()?; let fields = deployment_source .fields .iter() .map(|field_source| { - Ok(GuiFieldDefinitionCfg { + Ok(OrderBuilderFieldDefinitionCfg { binding: field_source.binding.clone(), name: field_source.name.clone(), description: field_source.description.clone(), @@ -188,16 +189,20 @@ impl GuiConfigSourceCfg { .as_ref() .map(|presets| { presets - .iter() - .enumerate() - .map(|(i, preset)| { - Ok(GuiPresetCfg { - id: i.to_string(), - name: preset.name.clone(), - value: preset.value.clone(), - }) - }) - .collect::, ParseGuiConfigSourceError>>() + .iter() + .enumerate() + .map(|(i, preset)| { + Ok(OrderBuilderPresetCfg { + id: i.to_string(), + name: preset.name.clone(), + value: preset.value.clone(), + }) + }) + .collect::, + ParseOrderBuilderConfigSourceError, + >>( + ) }) .transpose()?, default: field_source.default.clone(), @@ -205,11 +210,11 @@ impl GuiConfigSourceCfg { validation: field_source.validation.clone(), }) }) - .collect::, ParseGuiConfigSourceError>>()?; + .collect::, ParseOrderBuilderConfigSourceError>>()?; Ok(( deployment_name.clone(), - GuiDeploymentCfg { + OrderBuilderDeploymentCfg { document: default_document(), key: deployment_name.to_string(), deployment, @@ -222,19 +227,19 @@ impl GuiConfigSourceCfg { }, )) }) - .collect::, ParseGuiConfigSourceError>>()?; + .collect::, ParseOrderBuilderConfigSourceError>>()?; - Ok(GuiCfg { + Ok(OrderBuilderCfg { name: self.name, description: self.description, short_description: self.short_description, - deployments: gui_deployments, + deployments: order_builder_deployments, }) } } #[derive(Error, Debug)] -pub enum ParseGuiConfigSourceError { +pub enum ParseOrderBuilderConfigSourceError { #[error("Deployment not found: {0}")] DeploymentNotFoundError(String), #[error("Token not found: {0}")] @@ -245,22 +250,22 @@ pub enum ParseGuiConfigSourceError { UnitsError(#[from] UnitsError), } -// Config for Gui +// Config for OrderBuilder #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct GuiPresetCfg { +pub struct OrderBuilderPresetCfg { pub id: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub name: Option, pub value: String, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(GuiPresetCfg); +impl_wasm_traits!(OrderBuilderPresetCfg); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct GuiDepositCfg { +pub struct OrderBuilderDepositCfg { pub token_key: String, pub token: Option>, #[cfg_attr(target_family = "wasm", tsify(optional))] @@ -269,11 +274,11 @@ pub struct GuiDepositCfg { pub validation: Option, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(GuiDepositCfg); +impl_wasm_traits!(OrderBuilderDepositCfg); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct GuiSelectTokensCfg { +pub struct OrderBuilderSelectTokensCfg { pub key: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub name: Option, @@ -283,7 +288,7 @@ pub struct GuiSelectTokensCfg { #[derive(Debug, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct GuiDeploymentCfg { +pub struct OrderBuilderDeploymentCfg { #[serde(skip, default = "default_document")] pub document: Arc>, pub key: String, @@ -291,15 +296,15 @@ pub struct GuiDeploymentCfg { pub name: String, pub description: String, pub short_description: Option, - pub deposits: Vec, - pub fields: Vec, + pub deposits: Vec, + pub fields: Vec, #[cfg_attr(target_family = "wasm", tsify(optional))] - pub select_tokens: Option>, + pub select_tokens: Option>, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(GuiDeploymentCfg); +impl_wasm_traits!(OrderBuilderDeploymentCfg); -impl PartialEq for GuiDeploymentCfg { +impl PartialEq for OrderBuilderDeploymentCfg { fn eq(&self, other: &Self) -> bool { self.key == other.key && self.deployment == other.deployment @@ -314,13 +319,13 @@ impl PartialEq for GuiDeploymentCfg { #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] #[serde(rename_all = "camelCase")] -pub struct GuiFieldDefinitionCfg { +pub struct OrderBuilderFieldDefinitionCfg { pub binding: String, pub name: String, #[cfg_attr(target_family = "wasm", tsify(optional))] pub description: Option, #[cfg_attr(target_family = "wasm", tsify(optional))] - pub presets: Option>, + pub presets: Option>, #[cfg_attr(target_family = "wasm", tsify(optional))] pub default: Option, #[cfg_attr(target_family = "wasm", tsify(optional))] @@ -329,23 +334,23 @@ pub struct GuiFieldDefinitionCfg { pub validation: Option, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(GuiFieldDefinitionCfg); +impl_wasm_traits!(OrderBuilderFieldDefinitionCfg); #[derive(Debug, PartialEq, Serialize, Deserialize, Clone)] #[cfg_attr(target_family = "wasm", derive(Tsify))] -pub struct GuiCfg { +pub struct OrderBuilderCfg { pub name: String, pub description: String, pub short_description: Option, #[cfg_attr( target_family = "wasm", serde(serialize_with = "serialize_hashmap_as_object"), - tsify(optional, type = "Record") + tsify(optional, type = "Record") )] - pub deployments: HashMap, + pub deployments: HashMap, } #[cfg(target_family = "wasm")] -impl_wasm_traits!(GuiCfg); +impl_wasm_traits!(OrderBuilderCfg); #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[cfg_attr(target_family = "wasm", derive(Tsify))] @@ -358,13 +363,13 @@ pub struct NameAndDescriptionCfg { #[cfg(target_family = "wasm")] impl_wasm_traits!(NameAndDescriptionCfg); -impl GuiCfg { - pub fn check_gui_key_exists( +impl OrderBuilderCfg { + pub fn check_builder_key_exists( documents: Vec>>, ) -> Result { for document in documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if optional_hash(&document_read, "gui").is_some() { + if optional_hash(&document_read, "builder").is_some() { return Ok(true); } } @@ -379,12 +384,12 @@ impl GuiCfg { for document in documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if let Some(gui) = optional_hash(&document_read, "gui") { - let deployments = gui + if let Some(builder) = optional_hash(&document_read, "builder") { + let deployments = builder .get(&StrictYaml::String("deployments".to_string())) .ok_or(YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), })?; if let StrictYaml::Hash(deployments_hash) = deployments { @@ -399,7 +404,7 @@ impl GuiCfg { field: "deployments".to_string(), expected: "a map".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), }); } } @@ -411,13 +416,13 @@ impl GuiCfg { pub fn parse_select_tokens( documents: Vec>>, deployment_key: &str, - ) -> Result>, YamlError> { + ) -> Result>, YamlError> { for document in documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if let Some(gui) = optional_hash(&document_read, "gui") { + if let Some(builder) = optional_hash(&document_read, "builder") { if let Some(StrictYaml::Hash(deployments_hash)) = - gui.get(&StrictYaml::String("deployments".to_string())) + builder.get(&StrictYaml::String("deployments".to_string())) { if let Some(StrictYaml::Hash(deployment_hash)) = deployments_hash.get(&StrictYaml::String(deployment_key.to_string())) @@ -428,9 +433,9 @@ impl GuiCfg { let mut result = Vec::new(); for (index, token) in tokens.iter().enumerate() { if let StrictYaml::Hash(token_hash) = token { - let key = get_hash_value(token_hash, "key", Some(format!("key string missing for select-token index: {index} in gui deployment: {deployment_key}")))?.as_str().ok_or(YamlError::Field { + let key = get_hash_value(token_hash, "key", Some(format!("key string missing for select-token index: {index} in builder deployment: {deployment_key}")))?.as_str().ok_or(YamlError::Field { kind: FieldErrorKind::Missing("key".to_string()), - location: format!("select-token index: {index} in gui deployment: {deployment_key}"), + location: format!("select-token index: {index} in builder deployment: {deployment_key}"), })?; let name = get_hash_value_as_option(token_hash, "name") .map(|s| s.as_str()) @@ -439,7 +444,7 @@ impl GuiCfg { get_hash_value_as_option(token_hash, "description") .map(|s| s.as_str()) .unwrap_or_default(); - result.push(GuiSelectTokensCfg { + result.push(OrderBuilderSelectTokensCfg { key: key.to_string(), name: name.map(|s| s.to_string()), description: description.map(|s| s.to_string()), @@ -452,7 +457,7 @@ impl GuiCfg { } else { return Err(YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), }); } } @@ -466,23 +471,23 @@ impl GuiCfg { for document in documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if let Some(gui) = optional_hash(&document_read, "gui") { + if let Some(builder) = optional_hash(&document_read, "builder") { let name = require_string( - get_hash_value(gui, "name", Some("gui".to_string()))?, + get_hash_value(builder, "name", Some("builder".to_string()))?, None, - Some("gui".to_string()), + Some("builder".to_string()), )?; let description = require_string( - get_hash_value(gui, "description", Some("gui".to_string()))?, + get_hash_value(builder, "description", Some("builder".to_string()))?, None, - Some("gui".to_string()), + Some("builder".to_string()), )?; let short_description = require_string( - get_hash_value(gui, "short-description", Some("gui".to_string()))?, + get_hash_value(builder, "short-description", Some("builder".to_string()))?, None, - Some("gui".to_string()), + Some("builder".to_string()), )?; return Ok(NameAndDescriptionCfg { @@ -494,7 +499,7 @@ impl GuiCfg { } Err(YamlError::Field { kind: FieldErrorKind::Missing("name/description".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), }) } @@ -506,12 +511,12 @@ impl GuiCfg { for document in documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if let Some(gui) = optional_hash(&document_read, "gui") { - let deployments = gui + if let Some(builder) = optional_hash(&document_read, "builder") { + let deployments = builder .get(&StrictYaml::String("deployments".to_string())) .ok_or(YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), })? .as_hash() .ok_or(YamlError::Field { @@ -519,12 +524,12 @@ impl GuiCfg { field: "deployments".to_string(), expected: "a map".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), })?; for (key_yaml, deployment_yaml) in deployments { let deployment_key = key_yaml.as_str().unwrap_or_default().to_string(); - let location = format!("gui deployment '{deployment_key}'"); + let location = format!("builder deployment '{deployment_key}'"); let name = require_string(deployment_yaml, Some("name"), Some(location.clone()))?; @@ -556,13 +561,13 @@ impl GuiCfg { documents: Vec>>, deployment_key: &str, field_binding: &str, - ) -> Result>, YamlError> { + ) -> Result>, YamlError> { for document in documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if let Some(gui) = optional_hash(&document_read, "gui") { + if let Some(builder) = optional_hash(&document_read, "builder") { if let Some(StrictYaml::Hash(deployments_hash)) = - gui.get(&StrictYaml::String("deployments".to_string())) + builder.get(&StrictYaml::String("deployments".to_string())) { if let Some(StrictYaml::Hash(deployment_hash)) = deployments_hash.get(&StrictYaml::String(deployment_key.to_string())) @@ -585,11 +590,11 @@ impl GuiCfg { preset_yaml, Some("value"), Some(format!( - "preset index '{preset_index}' for field index '{field_index}' in gui deployment '{deployment_key}'", + "preset index '{preset_index}' for field index '{field_index}' in builder deployment '{deployment_key}'", )) )?; - Ok(GuiPresetCfg { + Ok(OrderBuilderPresetCfg { id: preset_index.to_string(), name, value, @@ -604,7 +609,7 @@ impl GuiCfg { } else { return Err(YamlError::Field { kind: FieldErrorKind::Missing("binding".to_string()), - location: format!("field index: {field_index} in gui deployment '{deployment_key}'"), + location: format!("field index: {field_index} in builder deployment '{deployment_key}'"), }); } } @@ -612,7 +617,7 @@ impl GuiCfg { } else { return Err(YamlError::Field { kind: FieldErrorKind::Missing("fields".to_string()), - location: format!("gui deployment '{deployment_key}'"), + location: format!("builder deployment '{deployment_key}'"), }); } } @@ -622,7 +627,7 @@ impl GuiCfg { field: "deployments".to_string(), expected: "a map".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), }); } } @@ -637,18 +642,18 @@ impl GuiCfg { continue; }; - let gui_key = StrictYaml::String("gui".to_string()); - let Some(gui_value) = root_hash.get(&gui_key) else { + let builder_key = StrictYaml::String("builder".to_string()); + let Some(builder_value) = root_hash.get(&builder_key) else { continue; }; - let StrictYaml::Hash(ref gui_hash) = gui_value.clone() else { + let StrictYaml::Hash(ref builder_hash) = builder_value.clone() else { continue; }; - let mut sanitized_gui = Hash::new(); - for allowed_key in ALLOWED_GUI_KEYS.iter() { + let mut sanitized_builder = Hash::new(); + for allowed_key in ALLOWED_ORDER_BUILDER_KEYS.iter() { let key_yaml = StrictYaml::String(allowed_key.to_string()); - if let Some(v) = gui_hash.get(&key_yaml) { + if let Some(v) = builder_hash.get(&key_yaml) { if *allowed_key == "deployments" { if let StrictYaml::Hash(ref deployments_hash) = *v { let mut sanitized_deployments: Vec<(String, StrictYaml)> = Vec::new(); @@ -663,7 +668,8 @@ impl GuiCfg { }; let mut sanitized_deployment = Hash::new(); - for allowed_dep_key in ALLOWED_GUI_DEPLOYMENT_KEYS.iter() { + for allowed_dep_key in ALLOWED_ORDER_BUILDER_DEPLOYMENT_KEYS.iter() + { let dep_key_yaml = StrictYaml::String(allowed_dep_key.to_string()); if let Some(dep_v) = deployment_hash.get(&dep_key_yaml) { @@ -683,24 +689,25 @@ impl GuiCfg { new_deployments_hash.insert(StrictYaml::String(key), value); } - sanitized_gui.insert(key_yaml, StrictYaml::Hash(new_deployments_hash)); + sanitized_builder + .insert(key_yaml, StrictYaml::Hash(new_deployments_hash)); } else { - sanitized_gui.insert(key_yaml, v.clone()); + sanitized_builder.insert(key_yaml, v.clone()); } } else { - sanitized_gui.insert(key_yaml, v.clone()); + sanitized_builder.insert(key_yaml, v.clone()); } } } - root_hash.insert(gui_key, StrictYaml::Hash(sanitized_gui)); + root_hash.insert(builder_key, StrictYaml::Hash(sanitized_builder)); } Ok(()) } } -impl YamlParseableValue for GuiCfg { +impl YamlParseableValue for OrderBuilderCfg { fn parse_from_yaml( _: Vec>>, _: Option<&Context>, @@ -712,61 +719,63 @@ impl YamlParseableValue for GuiCfg { documents: Vec>>, context: Option<&Context>, ) -> Result, YamlError> { - let mut gui_res: Option = None; - let mut gui_deployments_res: HashMap = HashMap::new(); + let mut order_builder_res: Option = None; + let mut order_builder_deployments_res: HashMap = + HashMap::new(); let tokens = TokenCfg::parse_all_from_yaml(documents.clone(), context); for document in &documents { let document_read = document.read().map_err(|_| YamlError::ReadLockError)?; - if let Some(gui) = optional_hash(&document_read, "gui") { - let name = get_hash_value(gui, "name", Some("gui".to_string()))? + if let Some(builder) = optional_hash(&document_read, "builder") { + let name = get_hash_value(builder, "name", Some("builder".to_string()))? .as_str() .ok_or(YamlError::Field { kind: FieldErrorKind::InvalidType { field: "name".to_string(), expected: "a string".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), })?; - let description = get_hash_value(gui, "description", Some("gui".to_string()))? - .as_str() - .ok_or(YamlError::Field { - kind: FieldErrorKind::InvalidType { - field: "description".to_string(), - expected: "a string".to_string(), - }, - location: "gui".to_string(), - })?; + let description = + get_hash_value(builder, "description", Some("builder".to_string()))? + .as_str() + .ok_or(YamlError::Field { + kind: FieldErrorKind::InvalidType { + field: "description".to_string(), + expected: "a string".to_string(), + }, + location: "builder".to_string(), + })?; - let short_description = get_hash_value_as_option(gui, "short-description") + let short_description = get_hash_value_as_option(builder, "short-description") .map(|v| { v.as_str().ok_or(YamlError::Field { kind: FieldErrorKind::InvalidType { field: "short-description".to_string(), expected: "a string".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), }) }) .transpose()?; - if gui_res.is_none() { - gui_res = Some(GuiCfg { + if order_builder_res.is_none() { + order_builder_res = Some(OrderBuilderCfg { name: name.to_string(), description: description.to_string(), short_description: short_description.map(|d| d.to_string()), - deployments: gui_deployments_res.clone(), + deployments: order_builder_deployments_res.clone(), }); } - let deployments = gui + let deployments = builder .get(&StrictYaml::String("deployments".to_string())) .ok_or(YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), })? .as_hash() .ok_or(YamlError::Field { @@ -774,12 +783,12 @@ impl YamlParseableValue for GuiCfg { field: "deployments".to_string(), expected: "a map".to_string(), }, - location: "gui".to_string(), + location: "builder".to_string(), })?; for (deployment_name, deployment_yaml) in deployments { let deployment_name = deployment_name.as_str().unwrap_or_default().to_string(); - let location = format!("gui deployment '{deployment_name}'"); + let location = format!("builder deployment '{deployment_name}'"); if let Some(context) = context { if let Some(current_deployment) = context.get_current_deployment() { @@ -797,7 +806,7 @@ impl YamlParseableValue for GuiCfg { .iter() .enumerate() .map(|(select_token_index, select_token_value)| { - let location = format!("select-token index '{select_token_index}' in gui deployment '{deployment_name}'"); + let location = format!("select-token index '{select_token_index}' in builder deployment '{deployment_name}'"); select_token_value.as_hash().ok_or(YamlError::Field{ kind: FieldErrorKind::InvalidType { @@ -807,7 +816,7 @@ impl YamlParseableValue for GuiCfg { location: location.clone(), })?; - Ok(GuiSelectTokensCfg { + Ok(OrderBuilderSelectTokensCfg { key: require_string(select_token_value, Some("key"), Some(location.clone()))?, name: optional_string(select_token_value, "name"), description: optional_string(select_token_value, "description"), @@ -886,13 +895,13 @@ impl YamlParseableValue for GuiCfg { parse_deposit_validation(validation_yaml) }).transpose()?; - let gui_deposit = GuiDepositCfg { + let order_builder_deposit = OrderBuilderDepositCfg { token_key, token: deposit_token, presets, validation, }; - Ok(gui_deposit) + Ok(order_builder_deposit) }) .collect::, YamlError>>()?; @@ -932,12 +941,12 @@ impl YamlParseableValue for GuiCfg { )) )?; - let gui_preset = GuiPresetCfg { + let order_builder_preset = OrderBuilderPresetCfg { id: preset_index.to_string(), name, value, }; - Ok(gui_preset) + Ok(order_builder_preset) }) .collect::, YamlError>>()?), None => None, @@ -952,7 +961,7 @@ impl YamlParseableValue for GuiCfg { )) }).transpose()?; - let gui_field_definition = GuiFieldDefinitionCfg { + let order_builder_field_definition = OrderBuilderFieldDefinitionCfg { binding, name: interpolated_name, description: interpolated_description, @@ -961,11 +970,11 @@ impl YamlParseableValue for GuiCfg { show_custom_field, validation, }; - Ok(gui_field_definition) + Ok(order_builder_field_definition) }) .collect::, YamlError>>()?; - let gui_deployment = GuiDeploymentCfg { + let order_builder_deployment = OrderBuilderDeploymentCfg { document: document.clone(), key: deployment_name.clone(), deployment: Arc::new(deployment), @@ -977,21 +986,23 @@ impl YamlParseableValue for GuiCfg { select_tokens, }; - if gui_deployments_res.contains_key(&deployment_name) { + if order_builder_deployments_res.contains_key(&deployment_name) { return Err(YamlError::KeyShadowing( deployment_name.clone(), - "gui deployment".to_string(), + "builder deployment".to_string(), )); } - gui_deployments_res.insert(deployment_name, gui_deployment); + order_builder_deployments_res.insert(deployment_name, order_builder_deployment); } - if let Some(gui) = &mut gui_res { - gui.deployments.clone_from(&gui_deployments_res); + if let Some(builder) = &mut order_builder_res { + builder + .deployments + .clone_from(&order_builder_deployments_res); } } } - Ok(gui_res) + Ok(order_builder_res) } } @@ -1094,34 +1105,34 @@ mod tests { use strict_yaml_rust::StrictYaml; #[test] - fn test_gui_creation_success() { - let gui_config_source = GuiConfigSourceCfg { - name: "test-gui".to_string(), - description: "test-gui-description".to_string(), + fn test_order_builder_creation_success() { + let order_builder_config_source = OrderBuilderConfigSourceCfg { + name: "test-builder".to_string(), + description: "test-builder-description".to_string(), short_description: None, deployments: HashMap::from([( "test-deployment".to_string(), - GuiDeploymentSourceCfg { + OrderBuilderDeploymentSourceCfg { name: "test-deployment".to_string(), description: "test-deployment-description".to_string(), short_description: None, - deposits: vec![GuiDepositSourceCfg { + deposits: vec![OrderBuilderDepositSourceCfg { token: "test-token".to_string(), presets: Some(vec!["1.3".to_string(), "2.7".to_string()]), validation: None, }], fields: vec![ - GuiFieldDefinitionSourceCfg { + OrderBuilderFieldDefinitionSourceCfg { binding: "test-binding".to_string(), name: "test-name".to_string(), description: Some("test-description".to_string()), presets: Some(vec![ - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: Some("test-preset".to_string()), value: "0.015".to_string(), }, - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: Some("test-preset-2".to_string()), value: "0.3".to_string(), }, @@ -1130,16 +1141,16 @@ mod tests { show_custom_field: None, validation: None, }, - GuiFieldDefinitionSourceCfg { + OrderBuilderFieldDefinitionSourceCfg { binding: "test-binding-2".to_string(), name: "test-name-2".to_string(), description: Some("test-description-2".to_string()), presets: Some(vec![ - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: None, value: "3.2".to_string(), }, - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: None, value: "4.8".to_string(), }, @@ -1148,20 +1159,20 @@ mod tests { show_custom_field: Some(true), validation: None, }, - GuiFieldDefinitionSourceCfg { + OrderBuilderFieldDefinitionSourceCfg { binding: "test-binding-3".to_string(), name: "test-name-3".to_string(), description: Some("test-description-3".to_string()), presets: Some(vec![ - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: None, value: Address::default().to_string(), }, - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: None, value: "some-value".to_string(), }, - GuiPresetSourceCfg { + OrderBuilderPresetSourceCfg { name: None, value: "true".to_string(), }, @@ -1171,7 +1182,7 @@ mod tests { validation: None, }, ], - select_tokens: Some(vec![GuiSelectTokensCfg { + select_tokens: Some(vec![OrderBuilderSelectTokensCfg { key: "test-token".to_string(), name: Some("Test name".to_string()), description: Some("Test description".to_string()), @@ -1207,14 +1218,14 @@ mod tests { let deployments = HashMap::from([("test-deployment".to_string(), Arc::new(deployment))]); let tokens = HashMap::from([("test-token".to_string(), mock_token("test-token"))]); - let gui = gui_config_source - .try_into_gui(&deployments, &tokens) + let order_builder = order_builder_config_source + .try_into_order_builder(&deployments, &tokens) .unwrap(); - assert_eq!(gui.name, "test-gui"); - assert_eq!(gui.description, "test-gui-description"); - assert_eq!(gui.deployments.len(), 1); - let deployment = &gui.deployments.get("test-deployment").unwrap(); + assert_eq!(order_builder.name, "test-builder"); + assert_eq!(order_builder.description, "test-builder-description"); + assert_eq!(order_builder.deployments.len(), 1); + let deployment = &order_builder.deployments.get("test-deployment").unwrap(); assert_eq!(deployment.name, "test-deployment"); assert_eq!(deployment.description, "test-deployment-description"); assert_eq!(deployment.deposits.len(), 1); @@ -1264,7 +1275,7 @@ mod tests { assert_eq!(presets[2].value, "true".to_string()); assert_eq!( deployment.select_tokens, - Some(vec![GuiSelectTokensCfg { + Some(vec![OrderBuilderSelectTokensCfg { key: "test-token".to_string(), name: Some("Test name".to_string()), description: Some("Test description".to_string()), @@ -1273,7 +1284,7 @@ mod tests { } #[test] - fn test_parse_gui_from_yaml() { + fn test_parse_order_builder_from_yaml() { let yaml = r#" networks: network1: @@ -1287,15 +1298,16 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: test: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { kind: FieldErrorKind::Missing("name".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } ); let yaml = r#" @@ -1311,11 +1323,12 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: - test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1323,7 +1336,7 @@ gui: field: "name".to_string(), expected: "a string".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); let yaml = r#" @@ -1339,11 +1352,12 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1351,7 +1365,7 @@ gui: field: "name".to_string(), expected: "a string".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -1368,15 +1382,16 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { kind: FieldErrorKind::Missing("description".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } ); let yaml = r#" @@ -1392,12 +1407,13 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: - test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1405,7 +1421,7 @@ gui: field: "description".to_string(), expected: "a string".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); let yaml = r#" @@ -1421,12 +1437,13 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1434,7 +1451,7 @@ gui: field: "description".to_string(), expected: "a string".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -1451,16 +1468,17 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } ); let yaml = r#" @@ -1476,12 +1494,13 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test deployments: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1489,7 +1508,7 @@ gui: field: "deployments".to_string(), expected: "a map".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); let yaml = r#" @@ -1505,13 +1524,14 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test deployments: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1519,7 +1539,7 @@ gui: field: "deployments".to_string(), expected: "a map".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -1552,14 +1572,15 @@ orders: outputs: - token: token2 rainlang: registry1 -gui: +builder: name: test description: test deployments: deployment1: test: test "#; - let error = GuiCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); + let error = + OrderBuilderCfg::parse_from_yaml_optional(vec![get_document(yaml)], None).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1604,14 +1625,14 @@ deployments: "#; let yaml = r#" -gui: +builder: name: test description: test deployments: deployment1: test: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1620,19 +1641,19 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("name".to_string()), - location: "gui deployment 'deployment1'".to_string(), + location: "builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: deployment1: name: deployment1 "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1641,12 +1662,12 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("description".to_string()), - location: "gui deployment 'deployment1'".to_string(), + location: "builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1654,7 +1675,7 @@ gui: name: some name description: some description "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1663,12 +1684,12 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("deposits".to_string()), - location: "gui deployment 'deployment1'".to_string(), + location: "builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1678,7 +1699,7 @@ gui: deposits: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1687,12 +1708,12 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("token".to_string()), - location: "deposit index '0' in gui deployment 'deployment1'".to_string(), + location: "deposit index '0' in builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1704,7 +1725,7 @@ gui: presets: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1717,13 +1738,13 @@ gui: expected: "a string".to_string() }, location: - "presets list index '0' for deposit index '0' in gui deployment 'deployment1'" + "presets list index '0' for deposit index '0' in builder deployment 'deployment1'" .to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1735,7 +1756,7 @@ gui: presets: - "1" "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1744,12 +1765,12 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("fields".to_string()), - location: "gui deployment 'deployment1'".to_string(), + location: "builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1763,7 +1784,7 @@ gui: fields: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1772,12 +1793,12 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("binding".to_string()), - location: "fields list index '0' in gui deployment 'deployment1'".to_string(), + location: "fields list index '0' in builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1791,7 +1812,7 @@ gui: fields: - binding: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1800,12 +1821,12 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("name".to_string()), - location: "fields list index '0' in gui deployment 'deployment1'".to_string(), + location: "fields list index '0' in builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1823,7 +1844,7 @@ gui: - value: wrong: map "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1835,13 +1856,14 @@ gui: field: "value".to_string(), expected: "a string".to_string() }, - location: "preset index '0' for field index '0' in gui deployment 'deployment1'" - .to_string(), + location: + "preset index '0' for field index '0' in builder deployment 'deployment1'" + .to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1860,7 +1882,7 @@ gui: select-tokens: - test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1872,12 +1894,12 @@ gui: field: "select-token".to_string(), expected: "a map".to_string() }, - location: "select-token index '0' in gui deployment 'deployment1'".to_string(), + location: "select-token index '0' in builder deployment 'deployment1'".to_string(), } ); let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -1896,7 +1918,7 @@ gui: select-tokens: - test: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -1905,13 +1927,13 @@ gui: error, YamlError::Field { kind: FieldErrorKind::Missing("key".to_string()), - location: "select-token index '0' in gui deployment 'deployment1'".to_string(), + location: "select-token index '0' in builder deployment 'deployment1'".to_string(), } ); } #[test] - fn test_parse_gui_from_yaml_multiple_files() { + fn test_parse_order_builder_from_yaml_multiple_files() { let yaml_one = r#" networks: network1: @@ -1948,7 +1970,7 @@ deployments: deployment2: scenario: scenario1 order: order1 -gui: +builder: name: test description: test deployments: @@ -1966,7 +1988,7 @@ gui: - value: test "#; let yaml_two = r#" -gui: +builder: name: test description: test deployments: @@ -1983,28 +2005,28 @@ gui: presets: - value: test "#; - let res = GuiCfg::parse_from_yaml_optional( + let res = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(yaml_one), get_document(yaml_two)], None, ) .unwrap(); - let gui = res.unwrap(); - assert_eq!(gui.deployments.len(), 2); + let order_builder = res.unwrap(); + assert_eq!(order_builder.deployments.len(), 2); - let deployment = gui.deployments.get("deployment1").unwrap(); + let deployment = order_builder.deployments.get("deployment1").unwrap(); assert_eq!(deployment.name, "test"); assert_eq!(deployment.description, "test"); assert_eq!(deployment.deposits[0].token.as_ref().unwrap().key, "token1"); - let deployment = gui.deployments.get("deployment2").unwrap(); + let deployment = order_builder.deployments.get("deployment2").unwrap(); assert_eq!(deployment.name, "test another"); assert_eq!(deployment.description, "test another"); assert_eq!(deployment.deposits[0].token.as_ref().unwrap().key, "token2"); } #[test] - fn test_parse_gui_from_yaml_duplicate_key() { + fn test_parse_order_builder_from_yaml_duplicate_key() { let yaml_one = r#" networks: network1: @@ -2041,7 +2063,7 @@ deployments: deployment2: scenario: scenario1 order: order1 -gui: +builder: name: test description: test deployments: @@ -2059,7 +2081,7 @@ gui: - value: test "#; let yaml_two = r#" -gui: +builder: name: test description: test deployments: @@ -2076,7 +2098,7 @@ gui: presets: - value: test "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(yaml_one), get_document(yaml_two)], None, ) @@ -2084,7 +2106,7 @@ gui: assert_eq!( error, - YamlError::KeyShadowing("deployment1".to_string(), "gui deployment".to_string()) + YamlError::KeyShadowing("deployment1".to_string(), "builder deployment".to_string()) ); } @@ -2103,17 +2125,17 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test "#; - let error = GuiCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); + let error = OrderBuilderCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::Field { kind: FieldErrorKind::Missing("deployments".to_string()), - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -2130,13 +2152,13 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test deployments: test "#; - let error = GuiCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); + let error = OrderBuilderCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -2144,7 +2166,7 @@ gui: field: "deployments".to_string(), expected: "a map".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -2161,14 +2183,14 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test deployments: - test "#; - let error = GuiCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); + let error = OrderBuilderCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -2176,7 +2198,7 @@ gui: field: "deployments".to_string(), expected: "a map".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -2193,14 +2215,14 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test deployments: - test: test "#; - let error = GuiCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); + let error = OrderBuilderCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap_err(); assert_eq!( error, YamlError::Field { @@ -2208,7 +2230,7 @@ gui: field: "deployments".to_string(), expected: "a map".to_string() }, - location: "gui".to_string(), + location: "builder".to_string(), } ); @@ -2225,7 +2247,7 @@ tokens: token2: address: 0x0000000000000000000000000000000000000002 network: network1 -gui: +builder: name: test description: test deployments: @@ -2233,24 +2255,24 @@ gui: test2: test2 "#; - let keys = GuiCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap(); + let keys = OrderBuilderCfg::parse_deployment_keys(vec![get_document(yaml)]).unwrap(); assert_eq!(keys, vec!["test".to_string(), "test2".to_string()]); } #[test] - fn test_check_gui_key_exists() { + fn test_check_builder_key_exists() { let yaml = r#" - gui: + builder: name: test description: test "#; - let res = GuiCfg::check_gui_key_exists(vec![get_document(yaml)]).unwrap(); + let res = OrderBuilderCfg::check_builder_key_exists(vec![get_document(yaml)]).unwrap(); assert!(res); let yaml = r#" test: test "#; - let res = GuiCfg::check_gui_key_exists(vec![get_document(yaml)]).unwrap(); + let res = OrderBuilderCfg::check_builder_key_exists(vec![get_document(yaml)]).unwrap(); assert!(!res); } @@ -2290,7 +2312,7 @@ deployments: // Test deposit validation with all fields let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2308,14 +2330,14 @@ gui: - binding: test name: test "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let deployment = gui.deployments.get("deployment1").unwrap(); + let deployment = order_builder.deployments.get("deployment1").unwrap(); let deposit = &deployment.deposits[0]; let validation = deposit.validation.as_ref().unwrap(); @@ -2326,7 +2348,7 @@ gui: // Test deposit validation with partial fields let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2342,14 +2364,18 @@ gui: - binding: test name: test "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let deposit = &gui.deployments.get("deployment1").unwrap().deposits[0]; + let deposit = &order_builder + .deployments + .get("deployment1") + .unwrap() + .deposits[0]; let validation = deposit.validation.as_ref().unwrap(); assert_eq!(validation.minimum, Some("100".to_string())); @@ -2359,7 +2385,7 @@ gui: // Test deposit without validation let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2372,14 +2398,18 @@ gui: - binding: test name: test "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let deposit = &gui.deployments.get("deployment1").unwrap().deposits[0]; + let deposit = &order_builder + .deployments + .get("deployment1") + .unwrap() + .deposits[0]; assert!(deposit.validation.is_none()); } @@ -2419,7 +2449,7 @@ deployments: // Test number validation with all fields let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2438,14 +2468,14 @@ gui: maximum: "100" exclusive-maximum: "101" "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let field = &gui.deployments.get("deployment1").unwrap().fields[0]; + let field = &order_builder.deployments.get("deployment1").unwrap().fields[0]; if let Some(FieldValueValidationCfg::Number { minimum, exclusive_minimum, @@ -2463,7 +2493,7 @@ gui: // Test number validation with partial fields let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2480,14 +2510,14 @@ gui: minimum: "0" maximum: "100" "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let field = &gui.deployments.get("deployment1").unwrap().fields[0]; + let field = &order_builder.deployments.get("deployment1").unwrap().fields[0]; if let Some(FieldValueValidationCfg::Number { minimum, exclusive_minimum, @@ -2540,7 +2570,7 @@ deployments: // Test string validation with all fields let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2557,14 +2587,14 @@ gui: min-length: 5 max-length: 50 "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let field = &gui.deployments.get("deployment1").unwrap().fields[0]; + let field = &order_builder.deployments.get("deployment1").unwrap().fields[0]; if let Some(FieldValueValidationCfg::String { min_length, max_length, @@ -2578,7 +2608,7 @@ gui: // Test string validation with partial fields let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2594,14 +2624,14 @@ gui: type: string min-length: 1 "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let field = &gui.deployments.get("deployment1").unwrap().fields[0]; + let field = &order_builder.deployments.get("deployment1").unwrap().fields[0]; if let Some(FieldValueValidationCfg::String { min_length, max_length, @@ -2615,7 +2645,7 @@ gui: // Test string validation with no length constraints let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2630,14 +2660,14 @@ gui: validation: type: string "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let field = &gui.deployments.get("deployment1").unwrap().fields[0]; + let field = &order_builder.deployments.get("deployment1").unwrap().fields[0]; if let Some(FieldValueValidationCfg::String { min_length, max_length, @@ -2686,7 +2716,7 @@ deployments: // Test boolean validation let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2701,14 +2731,14 @@ gui: validation: type: boolean "#; - let gui = GuiCfg::parse_from_yaml_optional( + let order_builder = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) .unwrap() .unwrap(); - let field = &gui.deployments.get("deployment1").unwrap().fields[0]; + let field = &order_builder.deployments.get("deployment1").unwrap().fields[0]; if let Some(FieldValueValidationCfg::Boolean) = &field.validation { // Boolean validation type correctly parsed } else { @@ -2752,7 +2782,7 @@ deployments: // Test missing type field let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2767,7 +2797,7 @@ gui: validation: minimum: "0" "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -2780,14 +2810,14 @@ gui: field: "type".to_string(), expected: "a string".to_string() }, - location: "validation for field index '0' in gui deployment 'deployment1'" + location: "validation for field index '0' in builder deployment 'deployment1'" .to_string(), } ); // Test invalid type value let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2802,7 +2832,7 @@ gui: validation: type: invalid-type "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -2815,14 +2845,14 @@ gui: field: "type".to_string(), expected: "one of: number, string, boolean".to_string() }, - location: "validation for field index '0' in gui deployment 'deployment1'" + location: "validation for field index '0' in builder deployment 'deployment1'" .to_string(), } ); // Test invalid min-length value for string let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2838,7 +2868,7 @@ gui: type: string min-length: invalid "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -2851,14 +2881,14 @@ gui: field: "min-length".to_string(), expected: "a valid number".to_string() }, - location: "validation for field index '0' in gui deployment 'deployment1'" + location: "validation for field index '0' in builder deployment 'deployment1'" .to_string(), } ); // Test invalid max-length value for string let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -2874,7 +2904,7 @@ gui: type: string max-length: invalid "#; - let error = GuiCfg::parse_from_yaml_optional( + let error = OrderBuilderCfg::parse_from_yaml_optional( vec![get_document(&format!("{yaml_prefix}{yaml}"))], None, ) @@ -2887,17 +2917,17 @@ gui: field: "max-length".to_string(), expected: "a valid number".to_string() }, - location: "validation for field index '0' in gui deployment 'deployment1'" + location: "validation for field index '0' in builder deployment 'deployment1'" .to_string(), } ); } #[test] - fn test_sanitize_documents_drops_unknown_gui_keys() { + fn test_sanitize_documents_drops_unknown_builder_keys() { let yaml = r#" -gui: - name: test-gui +builder: + name: test-builder description: test description short-description: short desc unknown-key: should-be-dropped @@ -2910,24 +2940,26 @@ gui: unknown-deployment-key: also-dropped "#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); let doc_read = document.read().unwrap(); let StrictYaml::Hash(ref root) = *doc_read else { panic!("expected root hash"); }; - let gui = root.get(&StrictYaml::String("gui".to_string())).unwrap(); - let StrictYaml::Hash(ref gui_hash) = *gui else { - panic!("expected gui hash"); + let builder = root + .get(&StrictYaml::String("builder".to_string())) + .unwrap(); + let StrictYaml::Hash(ref builder_hash) = *builder else { + panic!("expected builder hash"); }; - assert!(gui_hash.contains_key(&StrictYaml::String("name".to_string()))); - assert!(gui_hash.contains_key(&StrictYaml::String("description".to_string()))); - assert!(gui_hash.contains_key(&StrictYaml::String("short-description".to_string()))); - assert!(gui_hash.contains_key(&StrictYaml::String("deployments".to_string()))); - assert!(!gui_hash.contains_key(&StrictYaml::String("unknown-key".to_string()))); + assert!(builder_hash.contains_key(&StrictYaml::String("name".to_string()))); + assert!(builder_hash.contains_key(&StrictYaml::String("description".to_string()))); + assert!(builder_hash.contains_key(&StrictYaml::String("short-description".to_string()))); + assert!(builder_hash.contains_key(&StrictYaml::String("deployments".to_string()))); + assert!(!builder_hash.contains_key(&StrictYaml::String("unknown-key".to_string()))); - let deployments = gui_hash + let deployments = builder_hash .get(&StrictYaml::String("deployments".to_string())) .unwrap(); let StrictYaml::Hash(ref deployments_hash) = *deployments else { @@ -2949,9 +2981,9 @@ gui: } #[test] - fn test_sanitize_documents_preserves_allowed_gui_key_order() { + fn test_sanitize_documents_preserves_allowed_builder_key_order() { let yaml = r#" -gui: +builder: deployments: deployment1: fields: [] @@ -2963,18 +2995,20 @@ gui: name: name "#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); let doc_read = document.read().unwrap(); let StrictYaml::Hash(ref root) = *doc_read else { panic!("expected root hash"); }; - let gui = root.get(&StrictYaml::String("gui".to_string())).unwrap(); - let StrictYaml::Hash(ref gui_hash) = *gui else { - panic!("expected gui hash"); + let builder = root + .get(&StrictYaml::String("builder".to_string())) + .unwrap(); + let StrictYaml::Hash(ref builder_hash) = *builder else { + panic!("expected builder hash"); }; - let keys: Vec = gui_hash + let keys: Vec = builder_hash .keys() .filter_map(|k| k.as_str().map(String::from)) .collect(); @@ -2983,7 +3017,7 @@ gui: vec!["name", "description", "short-description", "deployments"] ); - let deployments = gui_hash + let deployments = builder_hash .get(&StrictYaml::String("deployments".to_string())) .unwrap(); let StrictYaml::Hash(ref deployments_hash) = *deployments else { @@ -3006,7 +3040,7 @@ gui: #[test] fn test_sanitize_documents_deployments_lexicographic_order() { let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -3027,17 +3061,19 @@ gui: fields: [] "#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); let doc_read = document.read().unwrap(); let StrictYaml::Hash(ref root) = *doc_read else { panic!("expected root hash"); }; - let gui = root.get(&StrictYaml::String("gui".to_string())).unwrap(); - let StrictYaml::Hash(ref gui_hash) = *gui else { - panic!("expected gui hash"); + let builder = root + .get(&StrictYaml::String("builder".to_string())) + .unwrap(); + let StrictYaml::Hash(ref builder_hash) = *builder else { + panic!("expected builder hash"); }; - let deployments = gui_hash + let deployments = builder_hash .get(&StrictYaml::String("deployments".to_string())) .unwrap(); let StrictYaml::Hash(ref deployments_hash) = *deployments else { @@ -3052,47 +3088,49 @@ gui: } #[test] - fn test_sanitize_documents_handles_missing_gui_section() { + fn test_sanitize_documents_handles_missing_builder_section() { let yaml = r#" other: value "#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); let doc_read = document.read().unwrap(); let StrictYaml::Hash(ref root) = *doc_read else { panic!("expected root hash"); }; - assert!(!root.contains_key(&StrictYaml::String("gui".to_string()))); + assert!(!root.contains_key(&StrictYaml::String("builder".to_string()))); } #[test] fn test_sanitize_documents_handles_non_hash_root() { let yaml = r#"just a string"#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); } #[test] - fn test_sanitize_documents_skips_non_hash_gui() { + fn test_sanitize_documents_skips_non_hash_builder() { let yaml = r#" -gui: not-a-hash +builder: not-a-hash "#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); let doc_read = document.read().unwrap(); let StrictYaml::Hash(ref root) = *doc_read else { panic!("expected root hash"); }; - let gui = root.get(&StrictYaml::String("gui".to_string())).unwrap(); - assert_eq!(gui.as_str(), Some("not-a-hash")); + let builder = root + .get(&StrictYaml::String("builder".to_string())) + .unwrap(); + assert_eq!(builder.as_str(), Some("not-a-hash")); } #[test] fn test_sanitize_documents_drops_non_hash_deployments() { let yaml = r#" -gui: +builder: name: test description: test deployments: @@ -3104,17 +3142,19 @@ gui: invalid: not-a-hash "#; let document = get_document(yaml); - GuiCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); + OrderBuilderCfg::sanitize_documents(std::slice::from_ref(&document)).unwrap(); let doc_read = document.read().unwrap(); let StrictYaml::Hash(ref root) = *doc_read else { panic!("expected root hash"); }; - let gui = root.get(&StrictYaml::String("gui".to_string())).unwrap(); - let StrictYaml::Hash(ref gui_hash) = *gui else { - panic!("expected gui hash"); + let builder = root + .get(&StrictYaml::String("builder".to_string())) + .unwrap(); + let StrictYaml::Hash(ref builder_hash) = *builder else { + panic!("expected builder hash"); }; - let deployments = gui_hash + let deployments = builder_hash .get(&StrictYaml::String("deployments".to_string())) .unwrap(); let StrictYaml::Hash(ref deployments_hash) = *deployments else { diff --git a/crates/settings/src/yaml/context.rs b/crates/settings/src/yaml/context.rs index 3460a30f01..7a88d55735 100644 --- a/crates/settings/src/yaml/context.rs +++ b/crates/settings/src/yaml/context.rs @@ -8,10 +8,10 @@ use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; /// ContextProfile selects how YAML is parsed and what data is injected into the context. /// - Strict: Full validation mode. Parses all orders/deployments and requires all networks, /// tokens, and bindings to be present. No scoping, no select-token deferral. Use this for -/// batch validation, CLI, and non-GUI flows where the entire config must be consistent. -/// - Gui: UI-scoped mode. Scopes parsing to the selected deployment, derives its order, +/// batch validation, CLI, and non-builder flows where the entire config must be consistent. +/// - Builder: UI-scoped mode. Scopes parsing to the selected deployment, derives its order, /// injects select-tokens for that deployment, and avoids parsing unrelated orders so -/// handlebars/missing-token templates in other orders don't fail. Use this for GUI/WASM +/// handlebars/missing-token templates in other orders don't fail. Use this for builder/WASM /// flows where the user works within a single deployment at a time. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] #[cfg_attr(target_family = "wasm", derive(Tsify))] @@ -19,7 +19,7 @@ use wasm_bindgen_utils::{impl_wasm_traits, prelude::*}; pub enum ContextProfile { #[default] Strict, - Gui { + Builder { current_deployment: String, }, } @@ -31,13 +31,13 @@ impl ContextProfile { Self::Strict } - pub fn gui(current_deployment: String) -> Self { - Self::Gui { current_deployment } + pub fn builder(current_deployment: String) -> Self { + Self::Builder { current_deployment } } } #[derive(Debug, Clone, Default)] -pub struct GuiContext { +pub struct OrderBuilderContext { pub current_deployment: Option, pub current_order: Option, } @@ -52,7 +52,7 @@ pub struct YamlCache { pub struct Context { pub order: Option>, pub select_tokens: Option>, - pub gui_context: Option, + pub builder_context: Option, pub yaml_cache: Option, } @@ -164,23 +164,23 @@ impl OrderContext for Context { } } -pub trait GuiContextTrait { +pub trait OrderBuilderContextTrait { fn get_current_deployment(&self) -> Option<&String>; fn get_current_order(&self) -> Option<&String>; } -impl GuiContextTrait for Context { +impl OrderBuilderContextTrait for Context { fn get_current_deployment(&self) -> Option<&String> { - self.gui_context + self.builder_context .as_ref() - .and_then(|gui_context| gui_context.current_deployment.as_ref()) + .and_then(|builder_context| builder_context.current_deployment.as_ref()) } fn get_current_order(&self) -> Option<&String> { - self.gui_context + self.builder_context .as_ref() - .and_then(|gui_context| gui_context.current_order.as_ref()) + .and_then(|builder_context| builder_context.current_order.as_ref()) } } @@ -227,7 +227,7 @@ impl Context { Self { order: None, select_tokens: None, - gui_context: None, + builder_context: None, yaml_cache: None, } } @@ -237,7 +237,9 @@ impl Context { if let Some(context) = context { new_context.order.clone_from(&context.order); new_context.select_tokens.clone_from(&context.select_tokens); - new_context.gui_context.clone_from(&context.gui_context); + new_context + .builder_context + .clone_from(&context.builder_context); new_context.yaml_cache.clone_from(&context.yaml_cache); } new_context @@ -254,10 +256,10 @@ impl Context { } pub fn add_current_deployment(&mut self, deployment: String) -> &mut Self { - if let Some(gui_context) = self.gui_context.as_mut() { - gui_context.current_deployment = Some(deployment); + if let Some(builder_context) = self.builder_context.as_mut() { + builder_context.current_deployment = Some(deployment); } else { - self.gui_context = Some(GuiContext { + self.builder_context = Some(OrderBuilderContext { current_deployment: Some(deployment), current_order: None, }); @@ -266,10 +268,10 @@ impl Context { } pub fn add_current_order(&mut self, order: String) -> &mut Self { - if let Some(gui_context) = self.gui_context.as_mut() { - gui_context.current_order = Some(order); + if let Some(builder_context) = self.builder_context.as_mut() { + builder_context.current_order = Some(order); } else { - self.gui_context = Some(GuiContext { + self.builder_context = Some(OrderBuilderContext { current_deployment: None, current_order: Some(order), }); @@ -397,12 +399,12 @@ mod tests { assert_eq!(ContextProfile::strict(), ContextProfile::Strict); assert_eq!(ContextProfile::default(), ContextProfile::Strict); - let gui_full = ContextProfile::gui("deployment1".to_string()); - match gui_full { - ContextProfile::Gui { current_deployment } => { + let builder_full = ContextProfile::builder("deployment1".to_string()); + match builder_full { + ContextProfile::Builder { current_deployment } => { assert_eq!(current_deployment, "deployment1".to_string()); } - _ => panic!("expected gui context profile"), + _ => panic!("expected builder context profile"), } } @@ -628,7 +630,7 @@ mod tests { let mut context = Context::new(); context.add_current_deployment("deployment1".to_string()); assert_eq!( - context.gui_context.unwrap().current_deployment, + context.builder_context.unwrap().current_deployment, Some("deployment1".to_string()) ); } @@ -638,7 +640,7 @@ mod tests { let mut context = Context::new(); context.add_current_order("order1".to_string()); assert_eq!( - context.gui_context.unwrap().current_order, + context.builder_context.unwrap().current_order, Some("order1".to_string()) ); } @@ -687,15 +689,15 @@ mod tests { assert_eq!(new_context.order, context.order); assert_eq!(new_context.select_tokens, context.select_tokens); - assert!(new_context.gui_context.is_some()); + assert!(new_context.builder_context.is_some()); assert!(new_context.yaml_cache.is_some()); - let gui_context = new_context.gui_context.unwrap(); + let builder_context = new_context.builder_context.unwrap(); assert_eq!( - gui_context.current_deployment, + builder_context.current_deployment, Some("deployment1".to_string()) ); - assert_eq!(gui_context.current_order, Some("order1".to_string())); + assert_eq!(builder_context.current_order, Some("order1".to_string())); let yaml_cache = new_context.yaml_cache.unwrap(); assert_eq!( diff --git a/crates/settings/src/yaml/dotrain.rs b/crates/settings/src/yaml/dotrain.rs index 4c1456e795..60d8b43580 100644 --- a/crates/settings/src/yaml/dotrain.rs +++ b/crates/settings/src/yaml/dotrain.rs @@ -1,5 +1,7 @@ use super::{cache::Cache, orderbook::OrderbookYaml, sanitize_all_documents, ValidationConfig, *}; -use crate::{spec_version::SpecVersion, ChartCfg, DeploymentCfg, GuiCfg, OrderCfg, ScenarioCfg}; +use crate::{ + spec_version::SpecVersion, ChartCfg, DeploymentCfg, OrderBuilderCfg, OrderCfg, ScenarioCfg, +}; use serde::{ de::{self, IgnoredAny, MapAccess, SeqAccess, Visitor}, ser::SerializeStruct, @@ -177,14 +179,15 @@ impl DotrainYaml { self.expand_context_with_remote_networks(&mut context); self.expand_context_with_remote_tokens(&mut context); } - ContextProfile::Gui { current_deployment } => { + ContextProfile::Builder { current_deployment } => { self.expand_context_with_current_deployment(&mut context, current_deployment); self.expand_context_with_remote_networks(&mut context); self.expand_context_with_remote_tokens(&mut context); - if let Some(select_tokens) = - GuiCfg::parse_select_tokens(self.documents.clone(), current_deployment)? - { + if let Some(select_tokens) = OrderBuilderCfg::parse_select_tokens( + self.documents.clone(), + current_deployment, + )? { context .add_select_tokens(select_tokens.iter().map(|st| st.key.clone()).collect()); } @@ -213,12 +216,12 @@ impl DotrainYaml { let context = self.build_context(&self.profile)?; OrderCfg::parse_from_yaml(self.documents.clone(), key, Some(&context)) } - pub fn get_order_for_gui_deployment( + pub fn get_order_for_builder_deployment( &self, order_key: &str, deployment_key: &str, ) -> Result { - let context = self.build_context(&ContextProfile::gui(deployment_key.to_string()))?; + let context = self.build_context(&ContextProfile::builder(deployment_key.to_string()))?; OrderCfg::parse_from_yaml(self.documents.clone(), order_key, Some(&context)) } @@ -246,9 +249,13 @@ impl DotrainYaml { DeploymentCfg::parse_from_yaml(self.documents.clone(), key, Some(&context)) } - pub fn get_gui(&self, current_deployment: &str) -> Result, YamlError> { - let context = self.build_context(&ContextProfile::gui(current_deployment.to_string()))?; - GuiCfg::parse_from_yaml_optional(self.documents.clone(), Some(&context)) + pub fn get_order_builder( + &self, + current_deployment: &str, + ) -> Result, YamlError> { + let context = + self.build_context(&ContextProfile::builder(current_deployment.to_string()))?; + OrderBuilderCfg::parse_from_yaml_optional(self.documents.clone(), Some(&context)) } pub fn get_chart_keys(&self) -> Result, YamlError> { @@ -378,8 +385,8 @@ mod tests { context::{ContextProfile, YamlCacheTrait}, orderbook::OrderbookYamlValidation, }, - BinXOptionsCfg, BinXTransformCfg, DotOptionsCfg, GuiSelectTokensCfg, HexBinOptionsCfg, - HexBinTransformCfg, LineOptionsCfg, MarkCfg, RectYOptionsCfg, TransformCfg, + BinXOptionsCfg, BinXTransformCfg, DotOptionsCfg, HexBinOptionsCfg, HexBinTransformCfg, + LineOptionsCfg, MarkCfg, OrderBuilderSelectTokensCfg, RectYOptionsCfg, TransformCfg, TransformOutputsCfg, VaultType, }; use alloy::primitives::U256; @@ -445,8 +452,8 @@ mod tests { deployment2: order: order1 scenario: scenario1 - gui: - name: Test gui + builder: + name: Test builder description: Test description short-description: Test short description deployments: @@ -579,8 +586,8 @@ mod tests { deployment1: order: order1 scenario: scenario1.scenario2 - gui: - name: Test gui + builder: + name: Test builder description: Test description deployments: deployment1: @@ -697,11 +704,14 @@ mod tests { "order1" ); - let gui = dotrain_yaml.get_gui("deployment1").unwrap().unwrap(); - assert_eq!(gui.name, "Test gui"); - assert_eq!(gui.description, "Test description"); - assert_eq!(gui.deployments.len(), 1); - let deployment = gui.deployments.get("deployment1").unwrap(); + let order_builder = dotrain_yaml + .get_order_builder("deployment1") + .unwrap() + .unwrap(); + assert_eq!(order_builder.name, "Test builder"); + assert_eq!(order_builder.description, "Test description"); + assert_eq!(order_builder.deployments.len(), 1); + let deployment = order_builder.deployments.get("deployment1").unwrap(); assert_eq!(deployment.name, "Test deployment"); assert_eq!(deployment.description, "Test description"); assert_eq!(deployment.deposits.len(), 1); @@ -729,12 +739,12 @@ mod tests { Some("Test description".to_string()) ); - let details = GuiCfg::parse_order_details(dotrain_yaml.documents.clone()).unwrap(); - assert_eq!(details.name, "Test gui"); + let details = OrderBuilderCfg::parse_order_details(dotrain_yaml.documents.clone()).unwrap(); + assert_eq!(details.name, "Test builder"); assert_eq!(details.description, "Test description"); let deployment_details = - GuiCfg::parse_deployment_details(dotrain_yaml.documents.clone()).unwrap(); + OrderBuilderCfg::parse_deployment_details(dotrain_yaml.documents.clone()).unwrap(); assert_eq!( deployment_details.get("deployment1").unwrap().name, "Test deployment" @@ -745,41 +755,49 @@ mod tests { ); let deployment_keys = - GuiCfg::parse_deployment_keys(dotrain_yaml.documents.clone()).unwrap(); + OrderBuilderCfg::parse_deployment_keys(dotrain_yaml.documents.clone()).unwrap(); assert_eq!(deployment_keys.len(), 1); assert_eq!(deployment_keys[0], "deployment1"); let select_tokens = - GuiCfg::parse_select_tokens(dotrain_yaml.documents.clone(), "deployment1").unwrap(); + OrderBuilderCfg::parse_select_tokens(dotrain_yaml.documents.clone(), "deployment1") + .unwrap(); assert!(select_tokens.is_some()); assert_eq!( select_tokens.unwrap()[0], - GuiSelectTokensCfg { + OrderBuilderSelectTokensCfg { key: "token2".to_string(), name: Some("Test token".to_string()), description: Some("Test description".to_string()) } ); let select_tokens = - GuiCfg::parse_select_tokens(dotrain_yaml.documents.clone(), "deployment2").unwrap(); + OrderBuilderCfg::parse_select_tokens(dotrain_yaml.documents.clone(), "deployment2") + .unwrap(); assert!(select_tokens.is_none()); - let gui_context = dotrain_yaml - .build_context(&ContextProfile::gui("deployment1".to_string())) + let builder_context = dotrain_yaml + .build_context(&ContextProfile::builder("deployment1".to_string())) .unwrap(); assert_eq!( - gui_context - .gui_context + builder_context + .builder_context .as_ref() .and_then(|gc| gc.current_deployment.clone()), Some("deployment1".to_string()) ); - assert_eq!(gui_context.select_tokens, Some(vec!["token2".to_string()])); + assert_eq!( + builder_context.select_tokens, + Some(vec!["token2".to_string()]) + ); - let field_presets = - GuiCfg::parse_field_presets(dotrain_yaml.documents.clone(), "deployment1", "key1") - .unwrap() - .unwrap(); + let field_presets = OrderBuilderCfg::parse_field_presets( + dotrain_yaml.documents.clone(), + "deployment1", + "key1", + ) + .unwrap() + .unwrap(); assert_eq!(field_presets[0].id, "0"); assert_eq!(field_presets[0].name, None); assert_eq!(field_presets[0].value, "value2"); @@ -917,40 +935,40 @@ mod tests { let strict_ctx = dotrain_yaml.build_context(&ContextProfile::Strict).unwrap(); assert!(strict_ctx.get_remote_network("remote-net").is_some()); assert!(strict_ctx.get_remote_token("remote-token").is_some()); - assert!(strict_ctx.gui_context.is_none()); + assert!(strict_ctx.builder_context.is_none()); assert!(strict_ctx.order.is_none()); - let gui_ctx = dotrain_yaml - .build_context(&ContextProfile::gui("deployment1".to_string())) + let builder_ctx = dotrain_yaml + .build_context(&ContextProfile::builder("deployment1".to_string())) .unwrap(); - assert!(gui_ctx.get_remote_network("remote-net").is_some()); - assert!(gui_ctx.get_remote_token("remote-token").is_some()); + assert!(builder_ctx.get_remote_network("remote-net").is_some()); + assert!(builder_ctx.get_remote_token("remote-token").is_some()); assert_eq!( - gui_ctx - .gui_context + builder_ctx + .builder_context .as_ref() .and_then(|gc| gc.current_order.clone()), Some("order1".to_string()) ); assert_eq!( - gui_ctx - .gui_context + builder_ctx + .builder_context .as_ref() .and_then(|gc| gc.current_deployment.clone()), Some("deployment1".to_string()) ); assert_eq!( - gui_ctx.order.as_ref().map(|order| order.key.clone()), + builder_ctx.order.as_ref().map(|order| order.key.clone()), Some("order1".to_string()) ); let propagated = DotrainYaml::from_dotrain_yaml(dotrain_yaml); let propagated_ctx = propagated - .build_context(&ContextProfile::gui("deployment1".to_string())) + .build_context(&ContextProfile::builder("deployment1".to_string())) .unwrap(); assert_eq!( propagated_ctx - .gui_context + .builder_context .as_ref() .and_then(|gc| gc.current_order.clone()), Some("order1".to_string()) @@ -989,7 +1007,7 @@ mod tests { deployment1: order: order1 scenario: scenario1 - gui: + builder: deployments: deployment1: select-tokens: @@ -1013,13 +1031,13 @@ mod tests { } ); - let gui_yaml = DotrainYaml::new_with_profile( + let builder_yaml = DotrainYaml::new_with_profile( vec![yaml.to_string()], DotrainYamlValidation::default(), - ContextProfile::gui("deployment1".to_string()), + ContextProfile::builder("deployment1".to_string()), ) .unwrap(); - let order = gui_yaml.get_order("order1").unwrap(); + let order = builder_yaml.get_order("order1").unwrap(); assert_eq!(order.inputs[0].token, None); assert_eq!(order.outputs[0].token, None); } @@ -1030,17 +1048,17 @@ mod tests { let dotrain_yaml = DotrainYaml::new_with_profile( vec![yaml.clone()], DotrainYamlValidation::default(), - ContextProfile::gui("deployment1".to_string()), + ContextProfile::builder("deployment1".to_string()), ) .unwrap(); let serialized = serde_json::to_string(&dotrain_yaml).unwrap(); let round_tripped: DotrainYaml = serde_json::from_str(&serialized).unwrap(); match round_tripped.profile { - ContextProfile::Gui { current_deployment } => { + ContextProfile::Builder { current_deployment } => { assert_eq!(current_deployment, "deployment1"); } - _ => panic!("expected gui profile"), + _ => panic!("expected builder profile"), } } @@ -1290,8 +1308,11 @@ mod tests { let dotrain_yaml = DotrainYaml::new(vec![handlebars_yaml()], DotrainYamlValidation::default()).unwrap(); - let gui = dotrain_yaml.get_gui("deployment1").unwrap().unwrap(); - let deployment = gui.deployments.get("deployment1").unwrap(); + let order_builder = dotrain_yaml + .get_order_builder("deployment1") + .unwrap() + .unwrap(); + let deployment = order_builder.deployments.get("deployment1").unwrap(); assert_eq!( deployment.deployment.scenario.bindings.get("key1").unwrap(), @@ -1332,7 +1353,7 @@ deployments: deployment1: order: order1 scenario: scenario1 -gui: +builder: name: test description: test deployments: @@ -1390,7 +1411,7 @@ orders: DotrainYamlValidation::default(), ) .unwrap(); - let error = dotrain_yaml.get_gui("deployment1").unwrap_err(); + let error = dotrain_yaml.get_order_builder("deployment1").unwrap_err(); assert_eq!( error, YamlError::Field { @@ -1411,7 +1432,7 @@ orders: DotrainYamlValidation::default(), ) .unwrap(); - let error = dotrain_yaml.get_gui("deployment1").unwrap_err(); + let error = dotrain_yaml.get_order_builder("deployment1").unwrap_err(); assert_eq!( error, YamlError::Field { diff --git a/crates/settings/src/yaml/emitter.rs b/crates/settings/src/yaml/emitter.rs index 98218aa89f..b9cf0a0fd4 100644 --- a/crates/settings/src/yaml/emitter.rs +++ b/crates/settings/src/yaml/emitter.rs @@ -6,7 +6,7 @@ use crate::{ accounts::AccountCfg, local_db_remotes::LocalDbRemoteCfg, local_db_sync::LocalDbSyncCfg, metaboard::MetaboardCfg, remote_networks::RemoteNetworksCfg, remote_tokens::RemoteTokensCfg, sentry::Sentry, spec_version::SpecVersion, subgraph::SubgraphCfg, ChartCfg, DeploymentCfg, - GuiCfg, NetworkCfg, OrderCfg, OrderbookCfg, RainlangCfg, ScenarioCfg, TokenCfg, + NetworkCfg, OrderBuilderCfg, OrderCfg, OrderbookCfg, RainlangCfg, ScenarioCfg, TokenCfg, }; use std::sync::{Arc, RwLock}; use strict_yaml_rust::{strict_yaml::Hash, StrictYaml, StrictYamlEmitter}; @@ -24,7 +24,7 @@ const CANONICAL_ROOT_KEYS: &[&str] = &[ "scenarios", "deployments", "charts", - "gui", + "builder", "accounts", "remote-networks", "remote-tokens", @@ -52,7 +52,7 @@ pub fn validate_and_emit_documents( LocalDbRemoteCfg::parse_all_from_yaml(documents.to_vec(), context)?; LocalDbSyncCfg::parse_all_from_yaml(documents.to_vec(), context)?; - GuiCfg::parse_from_yaml_optional(documents.to_vec(), context)?; + OrderBuilderCfg::parse_from_yaml_optional(documents.to_vec(), context)?; RemoteTokensCfg::parse_from_yaml_optional(documents.to_vec(), context)?; validate_string_field::(documents)?; diff --git a/crates/settings/src/yaml/mod.rs b/crates/settings/src/yaml/mod.rs index 16bf52d3a2..51121fcdc6 100644 --- a/crates/settings/src/yaml/mod.rs +++ b/crates/settings/src/yaml/mod.rs @@ -548,7 +548,7 @@ pub fn sanitize_all_documents(documents: &[Arc>]) -> Result<( crate::ChartCfg::sanitize_documents(documents)?; crate::RainlangCfg::sanitize_documents(documents)?; crate::DeploymentCfg::sanitize_documents(documents)?; - crate::GuiCfg::sanitize_documents(documents)?; + crate::OrderBuilderCfg::sanitize_documents(documents)?; crate::LocalDbSyncCfg::sanitize_documents(documents)?; crate::NetworkCfg::sanitize_documents(documents)?; crate::OrderCfg::sanitize_documents(documents)?; diff --git a/crates/settings/src/yaml/orderbook.rs b/crates/settings/src/yaml/orderbook.rs index fb5360a848..10c5450f31 100644 --- a/crates/settings/src/yaml/orderbook.rs +++ b/crates/settings/src/yaml/orderbook.rs @@ -576,12 +576,12 @@ mod tests { let ob = OrderbookYaml::new_with_profile( sources.clone(), OrderbookYamlValidation::default(), - ContextProfile::Gui { + ContextProfile::Builder { current_deployment: "deployment1".to_string(), }, ) .unwrap(); - assert!(matches!(ob.profile, ContextProfile::Gui { .. })); + assert!(matches!(ob.profile, ContextProfile::Builder { .. })); let ob_default = OrderbookYaml::new(sources, OrderbookYamlValidation::default()).unwrap(); assert!(matches!(ob_default.profile, ContextProfile::Strict)); @@ -595,7 +595,7 @@ mod tests { let ob = OrderbookYaml::new_with_profile( vec![full_yaml()], OrderbookYamlValidation::default(), - ContextProfile::Gui { + ContextProfile::Builder { current_deployment: "deployment1".to_string(), }, ) @@ -604,10 +604,10 @@ mod tests { let serialized = serde_json::to_string(&ob).unwrap(); let round_tripped: OrderbookYaml = serde_json::from_str(&serialized).unwrap(); match round_tripped.profile { - ContextProfile::Gui { current_deployment } => { + ContextProfile::Builder { current_deployment } => { assert_eq!(current_deployment, "deployment1"); } - _ => panic!("expected gui profile"), + _ => panic!("expected builder profile"), } } diff --git a/crates/test_fixtures/Cargo.toml b/crates/test_fixtures/Cargo.toml index 404446079c..469a951b00 100644 --- a/crates/test_fixtures/Cargo.toml +++ b/crates/test_fixtures/Cargo.toml @@ -10,7 +10,7 @@ publish = false [dependencies] serde_json = { workspace = true } -rain_interpreter_test_fixtures = { path = "../../lib/rain.interpreter/crates/test_fixtures" } +rain_interpreter_test_fixtures = { package = "rainlang_test_fixtures", path = "../../lib/rain.interpreter/crates/test_fixtures" } rain-math-float.workspace = true alloy = { workspace = true, features = [ "node-bindings", diff --git a/lib/rain.interpreter b/lib/rain.interpreter index 1ea41cd004..2c041df702 160000 --- a/lib/rain.interpreter +++ b/lib/rain.interpreter @@ -1 +1 @@ -Subproject commit 1ea41cd004a926e3a964ba5498654f3cd538f139 +Subproject commit 2c041df70284be63edfdbec209ff17a318eee279 diff --git a/packages/orderbook/ARCHITECTURE.md b/packages/orderbook/ARCHITECTURE.md index 9294432a70..d172fa0617 100644 --- a/packages/orderbook/ARCHITECTURE.md +++ b/packages/orderbook/ARCHITECTURE.md @@ -8,7 +8,7 @@ The SDK is designed to work in browsers, Node.js, and hybrid runtimes. It embeds ## Overview - Purpose - - Provide a WASM‑backed API for: YAML parsing/validation, orderbook queries (subgraph), quoting, vault management, transaction calldata generation (add/remove, deposit/withdraw), GUI helpers to deploy orders from dotrain, and low‑level hashing/ABI helpers. + - Provide a WASM‑backed API for: YAML parsing/validation, orderbook queries (subgraph), quoting, vault management, transaction calldata generation (add/remove, deposit/withdraw), builder helpers to deploy orders from dotrain, and low‑level hashing/ABI helpers. - Targets - ESM (browser) and CJS (Node.js) builds are both published. - The WASM is base64‑embedded to avoid runtime `fetch`/`fs` requirements. @@ -18,7 +18,7 @@ The SDK is designed to work in browsers, Node.js, and hybrid runtimes. It embeds Typical import ```ts -import { RaindexClient, DotrainOrderGui, parseYaml, getOrderHash } from "@rainlanguage/orderbook"; +import { RaindexClient, RaindexOrderBuilder, parseYaml, getOrderHash } from "@rainlanguage/orderbook"; ``` @@ -83,7 +83,7 @@ The package re‑exports the WASM‑bound API from the Rust crates. Representati - High‑level classes (selected) - `RaindexClient` — orderbook queries (orders, trades, vaults, quotes, transactions) across configured networks/subgraphs. Constructor is async (`await RaindexClient.new(...)`) and accepts optional `queryCallback` (for applying fetched records to the local DB), `wipeCallback` (for cleaning up stale data during full re-sync), and `statusCallback` (for reporting sync progress/errors to the caller) args for local DB sync when the YAML has `local-db-sync` sections. The sync scheduler starts automatically when configured and shuts down via Drop. - `RaindexOrder`, `RaindexVault`, `RaindexTrade`, `RaindexTransaction`, `RaindexVaultsList`, etc. - - `DotrainOrder`, `DotrainOrderGui`, `DotrainRegistry` — dotrain parsing, GUI orchestration, registry fetching (including `getOrderbookYaml()` for token queries), and deployment calldata. + - `DotrainOrder`, `RaindexOrderBuilder`, `DotrainRegistry` — dotrain parsing, builder orchestration, registry fetching (including `getOrderbookYaml()` for token queries), and deployment calldata. - `OrderbookYaml` — typed access to networks, tokens (via `getTokens()`), orderbooks, subgraphs, deployers, accounts, metaboards. - `Float` — arbitrary‑precision float utilities used across the API. - Errors & results @@ -104,7 +104,7 @@ Notes on runtime behavior ## Testing & Documentation -- Tests: Vitest suites under `test/` validate representative flows: orders/vaults/trades queries, quoting, calldata generation, GUI flows, and error surfaces. +- Tests: Vitest suites under `test/` validate representative flows: orders/vaults/trades queries, quoting, calldata generation, builder flows, and error surfaces. - Docs: `npm run docs` builds TypeDoc from the emitted `.d.ts` for hosted API documentation. diff --git a/packages/orderbook/README.md b/packages/orderbook/README.md index bd1f65f9d4..d769c72de5 100644 --- a/packages/orderbook/README.md +++ b/packages/orderbook/README.md @@ -123,7 +123,7 @@ deployments: using-tokens-from: - https://tokens.coingecko.com/base/all.json -gui: +builder: name: Fixed limit description: Deploy a USDC -> WETH limit order on Base. short-description: Deploy a USDC -> WETH limit order on Base. @@ -501,7 +501,7 @@ const ordersResult = await client.getOrders([137], filters, 1); ### Load remote strategies with `DotrainRegistry` -If you maintain a hosted registry, instantiate the helper, inspect what it exposes, and pull down any dotrain/GUI definitions you need: +If you maintain a hosted registry, instantiate the helper, inspect what it exposes, and pull down any dotrain/builder definitions you need: ```ts import { DotrainRegistry } from '@rainlanguage/orderbook'; @@ -528,7 +528,7 @@ fixed-limit https://example.com/orders/fixed-limit.rain dca https://example.com/orders/dca.rain ``` -The SDK merges the shared settings YAML with each order's `.rain` content before you ever build a GUI. +The SDK merges the shared settings YAML with each order's `.rain` content before you ever build an order builder. #### Access tokens from registry settings @@ -557,16 +557,16 @@ const client = clientResult.value; const ordersResult = await client.getOrders([8453]); ``` -### Build a deployment GUI +### Build a deployment order builder -Any dotrain file that includes a `gui:` block plus the usual settings YAML is enough to drive `DotrainOrderGui`. The `FIXED_LIMIT_SOURCE` constant declared earlier already includes the required networks/tokens/rainlangs plus a full `gui` definition, so you can reference it directly (or trim it to your own bindings) instead of copying pieces of `settings.yaml` inline in this guide. Always cross-check the source you feed in with the latest definitions in [rainlanguage/rain.strategies](https://github.com/rainlanguage/rain.strategies); that repository tracks the real configurations our UI ships with. +Any dotrain file that includes a `builder:` block plus the usual settings YAML is enough to drive `RaindexOrderBuilder`. The `FIXED_LIMIT_SOURCE` constant declared earlier already includes the required networks/tokens/deployers plus a full `builder` definition, so you can reference it directly (or trim it to your own bindings) instead of copying pieces of `settings.yaml` inline in this guide. Always cross-check the source you feed in with the latest definitions in [rainlanguage/rain.strategies](https://github.com/rainlanguage/rain.strategies); that repository tracks the real configurations our UI ships with. -With that single source string (read from disk or built dynamically) you can drive the full GUI workflow: +With that single source string (read from disk or built dynamically) you can drive the full order builder workflow: ```ts -import { DotrainOrderGui } from '@rainlanguage/orderbook'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; -const dotrainWithGui = FIXED_LIMIT_SOURCE; +const dotrainWithBuilder = FIXED_LIMIT_SOURCE; const SAMPLE_YAML = ` ... rainlangs: @@ -581,72 +581,72 @@ orderbooks: ` const additionalSettings = [SAMPLE_YAML]; // optional extra YAML strings -const deploymentsResult = await DotrainOrderGui.getDeploymentKeys( - dotrainWithGui, +const deploymentsResult = await RaindexOrderBuilder.getDeploymentKeys( + dotrainWithBuilder, additionalSettings ); if (deploymentsResult.error) throw new Error(deploymentsResult.error.readableMsg); const [firstDeployment] = deploymentsResult.value; -const guiResult = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, +const builderResult = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, additionalSettings, firstDeployment ); -if (guiResult.error) throw new Error(guiResult.error.readableMsg); -const gui = guiResult.value; +if (builderResult.error) throw new Error(builderResult.error.readableMsg); +const builder = builderResult.value; -const configResult = gui.getAllGuiConfig(); +const configResult = builder.getAllBuilderConfig(); if (configResult.error) throw new Error(configResult.error.readableMsg); const config = configResult.value; -const selectTokensResult = gui.getSelectTokens(); +const selectTokensResult = builder.getSelectTokens(); if (selectTokensResult.error) throw new Error(selectTokensResult.error.readableMsg); const selectTokens = selectTokensResult.value; -const depositsResult = gui.getDeposits(); +const depositsResult = builder.getDeposits(); if (depositsResult.error) throw new Error(depositsResult.error.readableMsg); const deposits = depositsResult.value; -await gui.setSelectToken('input-token', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'); // USDC -await gui.setSelectToken('output-token', '0x4200000000000000000000000000000000000006'); // WETH -const fieldResult = gui.setFieldValue('fixed-io', '1850'); +await builder.setSelectToken('input-token', '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'); // USDC +await builder.setSelectToken('output-token', '0x4200000000000000000000000000000000000006'); // WETH +const fieldResult = builder.setFieldValue('fixed-io', '1850'); if (fieldResult.error) throw new Error(fieldResult.error.readableMsg); -const amountResult = gui.setFieldValue('amount-per-trade', '250'); +const amountResult = builder.setFieldValue('amount-per-trade', '250'); if (amountResult.error) throw new Error(amountResult.error.readableMsg); -await gui.setDeposit('usdc', '5000'); -const vaultIdResult = gui.setVaultId('input', 'usdc', '42'); +await builder.setDeposit('usdc', '5000'); +const vaultIdResult = builder.setVaultId('input', 'usdc', '42'); if (vaultIdResult.error) throw new Error(vaultIdResult.error.readableMsg); -const allowancesResult = await gui.checkAllowances('0xOwner'); +const allowancesResult = await builder.checkAllowances('0xOwner'); if (allowancesResult.error) throw new Error(allowancesResult.error.readableMsg); const allowances = allowancesResult.value; -const approvalCalldatasResult = await gui.generateApprovalCalldatas('0xOwner'); +const approvalCalldatasResult = await builder.generateApprovalCalldatas('0xOwner'); if (approvalCalldatasResult.error) throw new Error(approvalCalldatasResult.error.readableMsg); -const depositCalldatasResult = await gui.generateDepositCalldatas(); +const depositCalldatasResult = await builder.generateDepositCalldatas(); if (depositCalldatasResult.error) throw new Error(depositCalldatasResult.error.readableMsg); -const deploymentArgsResult = await gui.getDeploymentTransactionArgs('0xOwner'); +const deploymentArgsResult = await builder.getDeploymentTransactionArgs('0xOwner'); if (deploymentArgsResult.error) throw new Error(deploymentArgsResult.error.readableMsg); const { approvals, deploymentCalldata, orderbookAddress, chainId } = deploymentArgsResult.value; -const rainlangResult = await gui.getComposedRainlang(); +const rainlangResult = await builder.getComposedRainlang(); if (rainlangResult.error) throw new Error(rainlangResult.error.readableMsg); const composedRainlang = rainlangResult.value; -const serializedStateResult = gui.serializeState(); +const serializedStateResult = builder.serializeState(); if (!serializedStateResult.error) { localStorage.setItem('fixed-limit-state', serializedStateResult.value); } ``` -Serialize the GUI state and later revive it with `DotrainOrderGui.newFromState(dotrainText, additionalSettings, serializedState, callback)` if you want to skip re-entering form choices. +Serialize the builder state and later revive it with `RaindexOrderBuilder.newFromState(dotrainText, additionalSettings, serializedState, callback)` if you want to skip re-entering form choices. #### Deploy with wallet + fetch your order -`gui.getDeploymentTransactionArgs(owner)` returns a `DeploymentTransactionArgs` struct with: +`builder.getDeploymentTransactionArgs(owner)` returns a `DeploymentTransactionArgs` struct with: - `approvals: ExtendedApprovalCalldata[]` (each item contains `token`, `calldata`, and the token `symbol` for UX) - `deploymentCalldata: Hex` – a multicall that performs deposits (if required) and adds the order in one transaction @@ -663,7 +663,7 @@ A typical deployment flow is: ```ts import type { RaindexClient } from '@rainlanguage/orderbook'; -const deploymentArgsResult = await gui.getDeploymentTransactionArgs(owner); +const deploymentArgsResult = await builder.getDeploymentTransactionArgs(owner); if (deploymentArgsResult.error) throw new Error(deploymentArgsResult.error.readableMsg); const { approvals, deploymentCalldata, orderbookAddress, chainId } = deploymentArgsResult.value; @@ -683,7 +683,7 @@ const raindexOrder = await waitForOrderFromTx(client as RaindexClient, { }); ``` -After you have a local GUI-aware dotrain source, you can also fetch equivalent sources from a registry and run the same flow: +After you have a local dotrain source, you can also fetch equivalent sources from a registry and run the same flow: ```ts import { DotrainRegistry } from '@rainlanguage/orderbook'; @@ -692,17 +692,17 @@ const registryResult = await DotrainRegistry.new('https://example.com/registry.t if (registryResult.error) throw new Error(registryResult.error.readableMsg); const registry = registryResult.value; -const guiSourceResult = await registry.getGui('fixed-limit', 'base'); -if (guiSourceResult.error) throw new Error(guiSourceResult.error.readableMsg); -const guiFromRegistry = guiSourceResult.value; +const builderResult = await registry.getOrderBuilder('fixed-limit', 'base'); +if (builderResult.error) throw new Error(builderResult.error.readableMsg); +const builderFromRegistry = builderResult.value; -// guiFromRegistry is already a DotrainOrderGui instance, so you can reuse -// the same GUI helper steps shown above (select tokens, deposits, calldata, etc.). +// builderFromRegistry is already a RaindexOrderBuilder instance, so you can reuse +// the same builder helper steps shown above (select tokens, deposits, calldata, etc.). ``` ### Work directly with dotrain files -If you just need Rainlang composition (no GUI state), read the dotrain text plus shared settings yourself, instantiate a `DotrainOrder`, and then ask it to compose scenario/deployment/post-task Rainlang. The example below reuses `FIXED_LIMIT_SOURCE`, but you can replace it with the contents of any `.rain` file. +If you just need Rainlang composition (no builder state), read the dotrain text plus shared settings yourself, instantiate a `DotrainOrder`, and then ask it to compose scenario/deployment/post-task Rainlang. The example below reuses `FIXED_LIMIT_SOURCE`, but you can replace it with the contents of any `.rain` file. ```ts import { DotrainOrder } from '@rainlanguage/orderbook'; diff --git a/packages/orderbook/test/js_api/gui.test.ts b/packages/orderbook/test/js_api/builder.test.ts similarity index 81% rename from packages/orderbook/test/js_api/gui.test.ts rename to packages/orderbook/test/js_api/builder.test.ts index 5cc8fafc69..1f709e45bb 100644 --- a/packages/orderbook/test/js_api/gui.test.ts +++ b/packages/orderbook/test/js_api/builder.test.ts @@ -1,48 +1,31 @@ -import { decodeFunctionData, hexToBytes, toFunctionSelector } from 'viem'; +import { decodeFunctionData, hexToBytes } from 'viem'; import assert from 'assert'; import { afterAll, beforeAll, beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { - DotrainOrderGui, + RaindexOrderBuilder, ApprovalCalldataResult, DeploymentTransactionArgs, DepositCalldataResult, - GuiCfg, - GuiDeploymentCfg, - GuiFieldDefinitionCfg, - GuiPresetCfg, - GuiSelectTokensCfg, + OrderBuilderCfg, + OrderBuilderDeploymentCfg, + OrderBuilderFieldDefinitionCfg, + OrderBuilderPresetCfg, + OrderBuilderSelectTokensCfg, NameAndDescriptionCfg, TokenAllowance, TokenDeposit, TokenInfo, - AllGuiConfig, + AllBuilderConfig, WasmEncodedResult, FieldValue, OrderbookYaml } from '../../dist/cjs'; const SPEC_VERSION = OrderbookYaml.getCurrentSpecVersion().value; - -// Rainlang contract function selectors -const EXPRESSION_DEPLOYER_ADDRESS_SELECTOR = toFunctionSelector( - 'function expressionDeployerAddress() external view returns (address)' -); -const INTERPRETER_ADDRESS_SELECTOR = toFunctionSelector( - 'function interpreterAddress() external view returns (address)' -); -const STORE_ADDRESS_SELECTOR = toFunctionSelector( - 'function storeAddress() external view returns (address)' -); -const PARSER_ADDRESS_SELECTOR = toFunctionSelector( - 'function parserAddress() external view returns (address)' -); -const PARSE2_SELECTOR = toFunctionSelector( - 'function parse2(bytes calldata data) external view returns (bytes calldata bytecode)' -); import { getLocal } from 'mockttp'; -const guiConfig = ` -gui: +const builderConfig = ` +builder: name: Fixed limit description: Fixed limit order short-description: Buy WETH with USDC on Base. @@ -103,8 +86,8 @@ gui: presets: - value: "0" `; -const guiConfig2 = ` -gui: +const builderConfig2 = ` +builder: name: Test test description: Test test test deployments: @@ -128,8 +111,8 @@ gui: - value: "0xbeef" default: 10 `; -const guiConfig3 = ` -gui: +const builderConfig3 = ` +builder: name: Test test description: Test test test deployments: @@ -173,7 +156,7 @@ metaboards: some-network: http://localhost:8085/metaboard rainlangs: - some-rainlang: + some-deployer: network: some-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba @@ -200,7 +183,7 @@ tokens: scenarios: some-scenario: - rainlang: some-rainlang + rainlang: some-deployer bindings: test-binding: 5 scenarios: @@ -216,7 +199,7 @@ orders: outputs: - token: token2 vault-id: 1 - rainlang: some-rainlang + rainlang: some-deployer orderbook: some-orderbook deployments: @@ -253,7 +236,7 @@ metaboards: some-network: http://localhost:8085/metaboard rainlangs: - some-rainlang: + some-deployer: network: some-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba @@ -280,7 +263,7 @@ tokens: scenarios: some-scenario: - rainlang: some-rainlang + rainlang: some-deployer bindings: test-binding: 5 @@ -290,7 +273,7 @@ orders: - token: token1 outputs: - token: token2 - rainlang: some-rainlang + rainlang: some-deployer orderbook: some-orderbook deployments: @@ -325,7 +308,7 @@ metaboards: test: http://localhost:8085/metaboard rainlangs: - some-rainlang: + some-deployer: network: some-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba @@ -338,7 +321,7 @@ orderbooks: scenarios: some-scenario: - rainlang: some-rainlang + rainlang: some-deployer bindings: test-binding: 5 @@ -348,7 +331,7 @@ orders: - token: token1 outputs: - token: token2 - rainlang: some-rainlang + rainlang: some-deployer orderbook: some-orderbook deployments: @@ -372,7 +355,7 @@ const dotrainWithTokensMismatch = dotrain.replace( ); const dotrainForRemotes = ` version: ${SPEC_VERSION} -gui: +builder: name: Test description: Fixed limit order deployments: @@ -413,10 +396,10 @@ metaboards: test: http://localhost:8085/metaboard some-network: http://localhost:8085/metaboard rainlangs: - some-rainlang: + some-deployer: network: remote-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba - other-rainlang: + other-deployer: network: some-network address: 0xF14E09601A47552De6aBd3A0B165607FaFd2B5Ba orderbooks: @@ -455,23 +438,23 @@ tokens: symbol: T3 scenarios: some-scenario: - rainlang: some-rainlang + rainlang: some-deployer other-scenario: - rainlang: other-rainlang + rainlang: other-deployer orders: some-order: inputs: - token: token1 outputs: - token: token2 - rainlang: some-rainlang + rainlang: some-deployer orderbook: some-orderbook other-order: inputs: - token: token3 outputs: - token: token3 - rainlang: other-rainlang + rainlang: other-deployer orderbook: other-orderbook deployments: test-deployment: @@ -484,13 +467,13 @@ deployments: _: 10, _: 20; `; -const dotrainWithGui = ` -${guiConfig} +const dotrainWithBuilder = ` +${builderConfig} ${dotrain} `; -describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () { +describe('Rain Orderbook JS API Package Bindgen Tests - Builder', async function () { const mockServer = getLocal(); beforeAll(async () => { await mockServer.start(8085); @@ -514,6 +497,25 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () return result.value; }; + const mockRainlangRegistryCalls = async () => { + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0x10762802') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0x4501e517') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0xf2c4da93') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); + await mockServer + .forPost('/rpc-url') + .withBodyIncluding('0x0c1916a4') + .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); + }; + const metaBoardAbi = [ { type: 'function', @@ -528,14 +530,14 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () ] as const; it('should return available deployments', async () => { - const result = await DotrainOrderGui.getDeploymentKeys(dotrainWithGui); + const result = await RaindexOrderBuilder.getDeploymentKeys(dotrainWithBuilder); const deployments = extractWasmEncodedData(result); assert.equal(deployments.length, 2); assert.ok(deployments.includes('some-deployment')); assert.ok(deployments.includes('other-deployment')); }); - it('should initialize gui object', async () => { + it('should initialize builder object', async () => { // mock the rpc call to get token info mockServer .forPost('/rpc-url') @@ -544,35 +546,35 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000' ); - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); - const guiConfig = extractWasmEncodedData(gui.getGuiConfig()); - assert.equal(guiConfig.name, 'Fixed limit'); - assert.equal(guiConfig.description, 'Fixed limit order'); + const builderConfig = extractWasmEncodedData(builder.getBuilderConfig()); + assert.equal(builderConfig.name, 'Fixed limit'); + assert.equal(builderConfig.description, 'Fixed limit order'); }); - it('should initialize gui object with state update callback', async () => { + it('should initialize builder object with state update callback', async () => { const stateUpdateCallback = vi.fn(); - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment', stateUpdateCallback ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); - gui.executeStateUpdateCallback(); + builder.executeStateUpdateCallback(); assert.equal(stateUpdateCallback.mock.calls.length, 1); }); it('should get order details', async () => { - const result = DotrainOrderGui.getOrderDetails(dotrainWithGui, undefined); + const result = RaindexOrderBuilder.getOrderDetails(dotrainWithBuilder, undefined); const orderDetails = extractWasmEncodedData(result); assert.equal(orderDetails.name, 'Fixed limit'); assert.equal(orderDetails.description, 'Fixed limit order'); @@ -580,7 +582,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should get deployment details', async () => { - const result = DotrainOrderGui.getDeploymentDetails(dotrainWithGui, undefined); + const result = RaindexOrderBuilder.getDeploymentDetails(dotrainWithBuilder, undefined); const deploymentDetails = extractWasmEncodedData>(result); const entries = Array.from(deploymentDetails.entries()); assert.equal(entries[0][0], 'other-deployment'); @@ -592,8 +594,8 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should get deployment detail', async () => { - const result = DotrainOrderGui.getDeploymentDetail( - dotrainWithGui, + const result = RaindexOrderBuilder.getDeploymentDetail( + dotrainWithBuilder, undefined, 'other-deployment' ); @@ -603,12 +605,12 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should get current deployment details', async () => { - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); mockServer .forPost('/rpc-url') @@ -618,7 +620,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () ); const deploymentDetail = extractWasmEncodedData( - gui.getCurrentDeploymentDetails() + builder.getCurrentDeploymentDetails() ); assert.equal(deploymentDetail.name, 'Buy WETH with USDC on Base.'); @@ -644,20 +646,20 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - const dotrainWithGui = ` - ${guiConfig2} + const dotrainWithBuilder = ` + ${builderConfig2} ${dotrain} `; - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'other-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); - const token1TokenInfo = extractWasmEncodedData(await gui.getTokenInfo('token1')); - const token2TokenInfo = extractWasmEncodedData(await gui.getTokenInfo('token2')); + const token1TokenInfo = extractWasmEncodedData(await builder.getTokenInfo('token1')); + const token2TokenInfo = extractWasmEncodedData(await builder.getTokenInfo('token2')); assert.equal(token1TokenInfo.address, '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'); assert.equal(token1TokenInfo.decimals, 6); @@ -684,19 +686,19 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - const dotrainWithGui = ` - ${guiConfig2} + const dotrainWithBuilder = ` + ${builderConfig2} ${dotrain} `; - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'other-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); - const allTokenInfos = extractWasmEncodedData(await gui.getAllTokenInfos()); + const allTokenInfos = extractWasmEncodedData(await builder.getAllTokenInfos()); assert.equal(allTokenInfos.length, 2); assert.equal(allTokenInfos[0].address, '0xc2132d05d31c914a87c6611c10748aeb04b58e8f'); @@ -710,7 +712,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); describe('deposit tests', async () => { - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; let stateUpdateCallback: Mock; beforeEach(async () => { stateUpdateCallback = vi.fn(); @@ -720,72 +722,72 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000' ); - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment', stateUpdateCallback ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); }); it('should add deposit', async () => { - assert.equal(extractWasmEncodedData(gui.hasAnyDeposit()), false); + assert.equal(extractWasmEncodedData(builder.hasAnyDeposit()), false); - await gui.setDeposit('token1', '50.6'); - const deposits = extractWasmEncodedData(gui.getDeposits()); + await builder.setDeposit('token1', '50.6'); + const deposits = extractWasmEncodedData(builder.getDeposits()); assert.equal(deposits.length, 1); - assert.equal(extractWasmEncodedData(gui.hasAnyDeposit()), true); + assert.equal(extractWasmEncodedData(builder.hasAnyDeposit()), true); assert.equal(stateUpdateCallback.mock.calls.length, 1); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); it('should update deposit', async () => { - await gui.setDeposit('token1', '50.6'); - await gui.setDeposit('token1', '100.6'); - const deposits = extractWasmEncodedData(gui.getDeposits()); + await builder.setDeposit('token1', '50.6'); + await builder.setDeposit('token1', '100.6'); + const deposits = extractWasmEncodedData(builder.getDeposits()); assert.equal(deposits.length, 1); assert.equal(deposits[0].amount, '100.6'); assert.equal(stateUpdateCallback.mock.calls.length, 2); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); - it('should throw error if deposit token is not found in gui config', () => { - const result = gui.getDepositPresets('token3'); + it('should throw error if deposit token is not found in builder config', () => { + const result = builder.getDepositPresets('token3'); if (!result.error) expect.fail('Expected error'); - expect(result.error.msg).toBe('Deposit token not found in gui config: token3'); + expect(result.error.msg).toBe('Deposit token not found in builder config: token3'); expect(result.error.readableMsg).toBe( "The deposit token 'token3' was not found in the YAML configuration." ); }); it('should remove deposit', async () => { - await gui.setDeposit('token1', '50.6'); - const deposits = extractWasmEncodedData(gui.getDeposits()); + await builder.setDeposit('token1', '50.6'); + const deposits = extractWasmEncodedData(builder.getDeposits()); assert.equal(deposits.length, 1); - gui.unsetDeposit('token1'); - const depositsAfterRemove = extractWasmEncodedData(gui.getDeposits()); + builder.unsetDeposit('token1'); + const depositsAfterRemove = extractWasmEncodedData(builder.getDeposits()); assert.equal(depositsAfterRemove.length, 0); - await gui.setDeposit('token1', '50.6'); - assert.equal(extractWasmEncodedData(gui.getDeposits()).length, 1); + await builder.setDeposit('token1', '50.6'); + assert.equal(extractWasmEncodedData(builder.getDeposits()).length, 1); assert.equal(stateUpdateCallback.mock.calls.length, 3); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); it('should throw error if deposit amount is empty', async () => { - const result = await gui.setDeposit('token1', ''); + const result = await builder.setDeposit('token1', ''); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Deposit amount cannot be an empty string'); expect(result.error.readableMsg).toBe( @@ -794,7 +796,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should get deposit presets', async () => { - const presets = extractWasmEncodedData(gui.getDepositPresets('token1')); + const presets = extractWasmEncodedData(builder.getDepositPresets('token1')); assert.equal(presets.length, 5); assert.equal(presets[0], '0'); assert.equal(presets[1], '10'); @@ -803,10 +805,10 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () assert.equal(presets[4], '10000'); }); - it('should throw error if deposit token is not found in gui config', () => { - const result = gui.getDepositPresets('token2'); + it('should throw error if deposit token is not found in builder config', () => { + const result = builder.getDepositPresets('token2'); if (!result.error) expect.fail('Expected error'); - expect(result.error.msg).toBe('Deposit token not found in gui config: token2'); + expect(result.error.msg).toBe('Deposit token not found in builder config: token2'); expect(result.error.readableMsg).toBe( "The deposit token 'token2' was not found in the YAML configuration." ); @@ -814,7 +816,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); describe('field value tests', async () => { - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; let stateUpdateCallback: Mock; beforeEach(async () => { stateUpdateCallback = vi.fn(); @@ -824,33 +826,33 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000' ); - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment', stateUpdateCallback ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); }); it('should save the field value as presets', async () => { - const allFieldDefinitions = extractWasmEncodedData( - gui.getAllFieldDefinitions() + const allFieldDefinitions = extractWasmEncodedData( + builder.getAllFieldDefinitions() ); - gui.setFieldValue('binding-1', allFieldDefinitions[0].presets?.[0]?.value || ''); - assert.deepEqual(extractWasmEncodedData(gui.getFieldValue('binding-1')), { + builder.setFieldValue('binding-1', allFieldDefinitions[0].presets?.[0]?.value || ''); + assert.deepEqual(extractWasmEncodedData(builder.getFieldValue('binding-1')), { field: 'binding-1', value: allFieldDefinitions[0].presets?.[0]?.value || '', is_preset: true }); - gui.setFieldValue('binding-1', allFieldDefinitions[0].presets?.[1]?.value || ''); - assert.deepEqual(extractWasmEncodedData(gui.getFieldValue('binding-1')), { + builder.setFieldValue('binding-1', allFieldDefinitions[0].presets?.[1]?.value || ''); + assert.deepEqual(extractWasmEncodedData(builder.getFieldValue('binding-1')), { field: 'binding-1', value: allFieldDefinitions[0].presets?.[1]?.value || '', is_preset: true }); - gui.setFieldValue('binding-1', allFieldDefinitions[0].presets?.[2]?.value || ''); - assert.deepEqual(extractWasmEncodedData(gui.getFieldValue('binding-1')), { + builder.setFieldValue('binding-1', allFieldDefinitions[0].presets?.[2]?.value || ''); + assert.deepEqual(extractWasmEncodedData(builder.getFieldValue('binding-1')), { field: 'binding-1', value: allFieldDefinitions[0].presets?.[2]?.value || '', is_preset: true @@ -858,12 +860,12 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () assert.equal(stateUpdateCallback.mock.calls.length, 3); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); it('should save field value as custom values', async () => { - gui.setFieldValues([ + builder.setFieldValues([ { field: 'binding-1', value: '0x1234567890abcdef1234567890abcdef12345678' @@ -873,7 +875,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () value: '100' } ]); - gui.setFieldValues([ + builder.setFieldValues([ { field: 'binding-1', value: 'some-string' @@ -883,7 +885,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () value: 'true' } ]); - const fieldValues = extractWasmEncodedData(gui.getAllFieldValues()); + const fieldValues = extractWasmEncodedData(builder.getAllFieldValues()); assert.equal(fieldValues.length, 2); assert.deepEqual(fieldValues[0], { field: 'binding-1', @@ -898,12 +900,12 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () assert.equal(stateUpdateCallback.mock.calls.length, 4); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); it('should throw error during save if field binding is not found in field definitions', () => { - const result = gui.setFieldValue('binding-3', '1'); + const result = builder.setFieldValue('binding-3', '1'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Field binding not found: binding-3'); expect(result.error.readableMsg).toBe( @@ -912,32 +914,32 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should get field value', async () => { - gui.setFieldValue('binding-1', '0x1234567890abcdef1234567890abcdef12345678'); - let fieldValue = extractWasmEncodedData(gui.getFieldValue('binding-1')); + builder.setFieldValue('binding-1', '0x1234567890abcdef1234567890abcdef12345678'); + let fieldValue = extractWasmEncodedData(builder.getFieldValue('binding-1')); assert.deepEqual(fieldValue, { field: 'binding-1', value: '0x1234567890abcdef1234567890abcdef12345678', is_preset: true }); - gui.setFieldValue('binding-2', 'true'); - fieldValue = extractWasmEncodedData(gui.getFieldValue('binding-2')); + builder.setFieldValue('binding-2', 'true'); + fieldValue = extractWasmEncodedData(builder.getFieldValue('binding-2')); assert.deepEqual(fieldValue, { field: 'binding-2', value: 'true', is_preset: false }); - gui.setFieldValue('binding-1', 'some-string'); - fieldValue = extractWasmEncodedData(gui.getFieldValue('binding-1')); + builder.setFieldValue('binding-1', 'some-string'); + fieldValue = extractWasmEncodedData(builder.getFieldValue('binding-1')); assert.deepEqual(fieldValue, { field: 'binding-1', value: 'some-string', is_preset: true }); - gui.setFieldValue('binding-2', '100.5'); - fieldValue = extractWasmEncodedData(gui.getFieldValue('binding-2')); + builder.setFieldValue('binding-2', '100.5'); + fieldValue = extractWasmEncodedData(builder.getFieldValue('binding-2')); assert.deepEqual(fieldValue, { field: 'binding-2', value: '100.5', @@ -946,7 +948,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should throw error during get if field binding is not found', () => { - const result = gui.getFieldValue('binding-3'); + const result = builder.getFieldValue('binding-3'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Field binding not found: binding-3'); expect(result.error.readableMsg).toBe( @@ -955,19 +957,19 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should correctly filter field definitions', async () => { - const allFieldDefinitions = extractWasmEncodedData( - gui.getAllFieldDefinitions() + const allFieldDefinitions = extractWasmEncodedData( + builder.getAllFieldDefinitions() ); assert.equal(allFieldDefinitions.length, 2); - const fieldDefinitionsWithoutDefaults = extractWasmEncodedData( - gui.getAllFieldDefinitions(true) - ); + const fieldDefinitionsWithoutDefaults = extractWasmEncodedData< + OrderBuilderFieldDefinitionCfg[] + >(builder.getAllFieldDefinitions(true)); assert.equal(fieldDefinitionsWithoutDefaults.length, 1); assert.equal(fieldDefinitionsWithoutDefaults[0].binding, 'binding-1'); - const fieldDefinitionsWithDefaults = extractWasmEncodedData( - gui.getAllFieldDefinitions(false) + const fieldDefinitionsWithDefaults = extractWasmEncodedData( + builder.getAllFieldDefinitions(false) ); assert.equal(fieldDefinitionsWithDefaults.length, 1); assert.equal(fieldDefinitionsWithDefaults[0].binding, 'binding-2'); @@ -975,7 +977,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); describe('field definition tests', async () => { - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; beforeAll(async () => { mockServer .forPost('/rpc-url') @@ -983,22 +985,22 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000' ); - const result = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + const result = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment' ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); }); it('should get field definition', async () => { - const allFieldDefinitions = extractWasmEncodedData( - gui.getAllFieldDefinitions() + const allFieldDefinitions = extractWasmEncodedData( + builder.getAllFieldDefinitions() ); assert.equal(allFieldDefinitions.length, 2); - const fieldDefinition = extractWasmEncodedData( - gui.getFieldDefinition('binding-1') + const fieldDefinition = extractWasmEncodedData( + builder.getFieldDefinition('binding-1') ); assert.equal(fieldDefinition.name, 'Field 1 name'); assert.equal(fieldDefinition.description, 'Field 1 description'); @@ -1006,7 +1008,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () assert.equal(fieldDefinition.default, 'some-default-value'); assert.equal(fieldDefinition.showCustomField, undefined); - let presets = fieldDefinition.presets as GuiPresetCfg[]; + let presets = fieldDefinition.presets as OrderBuilderPresetCfg[]; assert.equal(presets[0].name, 'Preset 1'); assert.equal(presets[0].value, '0x1234567890abcdef1234567890abcdef12345678'); assert.equal(presets[1].name, 'Preset 2'); @@ -1014,10 +1016,10 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () assert.equal(presets[2].name, 'Preset 3'); assert.equal(presets[2].value, 'some-string'); - const fieldDefinition2 = extractWasmEncodedData( - gui.getFieldDefinition('binding-2') + const fieldDefinition2 = extractWasmEncodedData( + builder.getFieldDefinition('binding-2') ); - presets = fieldDefinition2.presets as GuiPresetCfg[]; + presets = fieldDefinition2.presets as OrderBuilderPresetCfg[]; assert.equal(presets[0].value, '99.2'); assert.equal(presets[1].value, '582.1'); assert.equal(presets[2].value, '648.239'); @@ -1026,7 +1028,7 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () }); it('should throw error during get if field binding is not found', () => { - const result = gui.getFieldDefinition('binding-3'); + const result = builder.getFieldDefinition('binding-3'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Field binding not found: binding-3'); expect(result.error.readableMsg).toBe( @@ -1037,9 +1039,9 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () describe('state management tests', async () => { let serializedState = - 'H4sIAAAAAAAA_21QzUrEMBBuqiiIBxGvguDV2GxCy-6yXpSVQlFQi38X6bZhu26a1DZViw_h0asvsPgEXr35POJNi0ndsjuHfJP5vsnMF2D8xapCSXMJByMejfgQqBoyVqbZ-4AV1FSVJc2IMeUtQ8eiQhvtOg0JriULClsIgXmP4eZNL5iLhEJO5YPIxrpvU2EsZdq1LCbCgMUil902attWloawyNhTpQDVCfTovu9uqPS59zXZ_uxN3l_st-9LE3c-XkOwDpYV7Vc7bGGgbfu_PkzjP5rfUA9wHAfM-KpZQsiOSml5FHj9m9I9ZHeDW37qnYcH-Mx2OyfXBRtGJDnehx7BF_Dxam9N9QgZ0wxGNGWiTCiXP9NL3cfLAQAA'; + 'H4sIAAAAAAAA_21QvU7DMBCOAwIJMSDEioTESoibKD9UZWAAyo-KgEhtWVBJTRPVtYPtgAoPwcjKC1Q8ASsbz4PYIOIciOgN_s73fee7z8j4iUVARaSyrlLWT9kAQQ0bC3_Z2x7NiQmVOc3wIWE1Q8csoIc3_YrEKSUzgDWM0bTHnOpNLyj5iFiMqDsuhrpvFTBRKqvbNuVxjyZcqnqIQ88WWWzlgj4UClScSI_ejZorkD42Pibr743J65P38tkxna235xgto3mgo2KHNQdp29G3D9P4jeo3lAN830f_fJWs67obkO5c3ByceYftIBgfHwXxvsB5ilsn7cu9827zujOQXfc-DFhLeqfbS9DDVUKE1ScZ5eMRYeoLJmrdMssBAAA='; let dotrain3: string; - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; beforeAll(async () => { mockServer .forPost('/rpc-url') @@ -1055,39 +1057,44 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function () .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - dotrain3 = `${guiConfig3} + dotrain3 = `${builderConfig3} ${dotrain}`; - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrain3, undefined, 'other-deployment' ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); - gui.setFieldValue( + builder.setFieldValue( 'test-binding', - extractWasmEncodedData(gui.getFieldDefinition('test-binding')) - .presets?.[0].value || '' - ); - await gui.setDeposit('token1', '50.6'); - await gui.setDeposit('token2', '100'); - gui.unsetSelectToken('token1'); - await gui.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); - gui.setVaultId('input', 'token1', '666'); - gui.setVaultId('output', 'token2', '333'); + extractWasmEncodedData( + builder.getFieldDefinition('test-binding') + ).presets?.[0].value || '' + ); + await builder.setDeposit('token1', '50.6'); + await builder.setDeposit('token2', '100'); + builder.unsetSelectToken('token1'); + await builder.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); + builder.setVaultId('input', 'token1', '666'); + builder.setVaultId('output', 'token2', '333'); }); - it('should serialize gui state', async () => { - const serialized = extractWasmEncodedData(gui.serializeState()); - expect(serialized).toBe(serializedState); + it('should serialize builder state', async () => { + const serialized = extractWasmEncodedData(builder.serializeState()); + assert.equal(serialized, serializedState); }); - it('should deserialize gui state', async () => { - const guiResult = await DotrainOrderGui.newFromState(dotrain3, undefined, serializedState); - const gui = extractWasmEncodedData(guiResult); + it('should deserialize builder state', async () => { + const builderResult = await RaindexOrderBuilder.newFromState( + dotrain3, + undefined, + serializedState + ); + const builder = extractWasmEncodedData(builderResult); - const fieldValues = extractWasmEncodedData(gui.getAllFieldValues()); + const fieldValues = extractWasmEncodedData(builder.getAllFieldValues()); assert.equal(fieldValues.length, 1); assert.deepEqual(fieldValues[0], { field: 'test-binding', @@ -1095,9 +1102,9 @@ ${dotrain}`; is_preset: true }); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), true); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token2')), true); - const deposits = extractWasmEncodedData(gui.getDeposits()); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), true); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token2')), true); + const deposits = extractWasmEncodedData(builder.getDeposits()); assert.equal(deposits.length, 2); assert.equal(deposits[0].token, 'token1'); assert.equal(deposits[0].amount, '50.6'); @@ -1106,17 +1113,21 @@ ${dotrain}`; assert.equal(deposits[1].amount, '100'); assert.equal(deposits[1].address, '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063'); - const result = gui.getCurrentDeployment(); - const guiDeployment = extractWasmEncodedData(result); - assert.equal(guiDeployment.deployment.order.inputs[0].vaultId, '0x29a'); - assert.equal(guiDeployment.deployment.order.outputs[0].vaultId, '0x14d'); + const result = builder.getCurrentDeployment(); + const builderDeployment = extractWasmEncodedData(result); + assert.equal(builderDeployment.deployment.order.inputs[0].vaultId, '0x29a'); + assert.equal(builderDeployment.deployment.order.outputs[0].vaultId, '0x14d'); }); it('should throw error if given dotrain is different', async () => { - let testDotrain = `${guiConfig} + let testDotrain = `${builderConfig} ${dotrainWithTokensMismatch}`; - const result = await DotrainOrderGui.newFromState(testDotrain, undefined, serializedState); + const result = await RaindexOrderBuilder.newFromState( + testDotrain, + undefined, + serializedState + ); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Deserialized dotrain mismatch'); expect(result.error.readableMsg).toBe( @@ -1133,26 +1144,34 @@ ${dotrainWithTokensMismatch}`; ); let testDotrain = ` -${guiConfig2} +${builderConfig2} ${dotrainWithoutVaultIds} `; - let result = await DotrainOrderGui.newWithDeployment( + let result = await RaindexOrderBuilder.newWithDeployment( testDotrain, undefined, 'other-deployment' ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); - let deployment1 = extractWasmEncodedData(gui.getCurrentDeployment()); + let deployment1 = extractWasmEncodedData( + builder.getCurrentDeployment() + ); assert.equal(deployment1.deployment.order.inputs[0].vaultId, undefined); assert.equal(deployment1.deployment.order.outputs[0].vaultId, undefined); - let serializedState = extractWasmEncodedData(gui.serializeState()); - const guiResult = await DotrainOrderGui.newFromState(testDotrain, undefined, serializedState); - gui = extractWasmEncodedData(guiResult); + let serializedState = extractWasmEncodedData(builder.serializeState()); + const builderResult = await RaindexOrderBuilder.newFromState( + testDotrain, + undefined, + serializedState + ); + builder = extractWasmEncodedData(builderResult); - let deployment2 = extractWasmEncodedData(gui.getCurrentDeployment()); + let deployment2 = extractWasmEncodedData( + builder.getCurrentDeployment() + ); assert.equal(deployment2.deployment.order.inputs[0].vaultId, undefined); assert.equal(deployment2.deployment.order.outputs[0].vaultId, undefined); }); @@ -1170,15 +1189,15 @@ ${dotrainWithoutVaultIds} .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000015db63900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a54657468657220555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553445400000000000000000000000000000000000000000000000000000000' ); - dotrain3 = `${guiConfig} + dotrain3 = `${builderConfig} ${dotrain}`; - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrain3, undefined, 'some-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); const { fieldDefinitionsWithoutDefaults, @@ -1186,7 +1205,7 @@ ${dotrain}`; deposits, orderInputs, orderOutputs - } = extractWasmEncodedData(gui.getAllGuiConfig()); + } = extractWasmEncodedData(builder.getAllBuilderConfig()); assert.equal(fieldDefinitionsWithoutDefaults.length, 1); assert.equal(fieldDefinitionsWithoutDefaults[0].binding, 'binding-2'); @@ -1206,7 +1225,7 @@ ${dotrain}`; }); describe('order operations tests', async () => { - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; beforeEach(async () => { // token1 info @@ -1227,16 +1246,16 @@ ${dotrain}`; ); let dotrain2 = ` - ${guiConfig2} + ${builderConfig2} ${dotrain} `; - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrain2, undefined, 'other-deployment' ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); }); it('checks input and output allowances', async () => { @@ -1248,10 +1267,10 @@ ${dotrain}`; '0x0000000000000000000000000000000000000000000000000000000000000001' ); - await gui.setDeposit('token2', '200'); + await builder.setDeposit('token2', '200'); const allowances = extractWasmEncodedData( - await gui.checkAllowances('0x1234567890abcdef1234567890abcdef12345678') + await builder.checkAllowances('0x1234567890abcdef1234567890abcdef12345678') ); assert.equal(allowances.length, 1); assert.equal(allowances[0].token, '0x8f3cf7ad23cd3cadbd9735aff958023239c6a063'); @@ -1288,11 +1307,11 @@ ${dotrain}`; '0x00000000000000000000000000000000000000000000003635C9ADC5DEA00000' ); - await gui.setDeposit('token1', '1000'); - await gui.setDeposit('token2', '5000'); + await builder.setDeposit('token1', '1000'); + await builder.setDeposit('token2', '5000'); const result = extractWasmEncodedData( - await gui.generateApprovalCalldatas('0x1234567890abcdef1234567890abcdef12345678') + await builder.generateApprovalCalldatas('0x1234567890abcdef1234567890abcdef12345678') ); // @ts-expect-error - result is valid @@ -1306,10 +1325,10 @@ ${dotrain}`; ); // Test no deposits case - gui.unsetDeposit('token1'); - gui.unsetDeposit('token2'); + builder.unsetDeposit('token1'); + builder.unsetDeposit('token2'); const emptyResult = extractWasmEncodedData( - await gui.generateApprovalCalldatas('0x1234567890abcdef1234567890abcdef12345678') + await builder.generateApprovalCalldatas('0x1234567890abcdef1234567890abcdef12345678') ); assert.equal(emptyResult, 'NoDeposits'); }); @@ -1330,11 +1349,11 @@ ${dotrain}`; '0x00000000000000000000000000000000000000000000010f0cf064dd59200000' ); - gui.unsetDeposit('token1'); - await gui.setDeposit('token2', '2000'); + builder.unsetDeposit('token1'); + await builder.setDeposit('token2', '2000'); const result = extractWasmEncodedData( - await gui.generateApprovalCalldatas('0x1234567890abcdef1234567890abcdef12345678') + await builder.generateApprovalCalldatas('0x1234567890abcdef1234567890abcdef12345678') ); // @ts-expect-error - result is valid @@ -1349,40 +1368,34 @@ ${dotrain}`; }); it('generates deposit calldatas', async () => { - // expressionDeployerAddress() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); - // interpreterAddress() call + // I_STORE()() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); - // storeAddress() call + // I_PARSER() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); - // parserAddress() call - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); // parse2() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') // 0x1234 encoded bytes .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - await gui.setDeposit('token1', '1000'); - await gui.setDeposit('token2', '5000'); + await builder.setDeposit('token1', '1000'); + await builder.setDeposit('token2', '5000'); const result = extractWasmEncodedData( - await gui.generateDepositCalldatas() + await builder.generateDepositCalldatas() ); // @ts-expect-error - result is valid @@ -1394,51 +1407,48 @@ ${dotrain}`; ); // Test no deposits case - gui.unsetDeposit('token1'); - gui.unsetDeposit('token2'); + builder.unsetDeposit('token1'); + builder.unsetDeposit('token2'); const emptyResult = extractWasmEncodedData( - await gui.generateDepositCalldatas() + await builder.generateDepositCalldatas() ); assert.equal(emptyResult, 'NoDeposits'); }); it('generates add order calldata', async () => { - // expressionDeployerAddress() call + await mockRainlangRegistryCalls(); await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); - // interpreterAddress() call + // I_STORE()() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); - // storeAddress() call + // I_PARSER() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); - // parserAddress() call - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); // parse2() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') // 0x1234 encoded bytes .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - gui.setFieldValue('test-binding', '10'); + builder.setFieldValue('test-binding', '10'); - const addOrderCalldata = extractWasmEncodedData(await gui.generateAddOrderCalldata()); + const addOrderCalldata = extractWasmEncodedData( + await builder.generateAddOrderCalldata() + ); assert.equal(addOrderCalldata.length, 2634); - let result = gui.getCurrentDeployment(); - const currentDeployment = extractWasmEncodedData(result); + let result = builder.getCurrentDeployment(); + const currentDeployment = extractWasmEncodedData(result); assert.deepEqual(currentDeployment.deployment.scenario.bindings, { 'test-binding': '10', 'another-binding': '300' @@ -1446,40 +1456,37 @@ ${dotrain}`; }); it('generates add order calldata without entering field value', async () => { - // expressionDeployerAddress() call + await mockRainlangRegistryCalls(); await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); - // interpreterAddress() call + // I_STORE()() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); - // storeAddress() call + // I_PARSER() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); - // parserAddress() call - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); // parse2() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') // 0x1234 encoded bytes .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - const addOrderCalldata = extractWasmEncodedData(await gui.generateAddOrderCalldata()); + const addOrderCalldata = extractWasmEncodedData( + await builder.generateAddOrderCalldata() + ); assert.equal(addOrderCalldata.length, 2634); - let result = gui.getCurrentDeployment(); - const currentDeployment = extractWasmEncodedData(result); + let result = builder.getCurrentDeployment(); + const currentDeployment = extractWasmEncodedData(result); assert.deepEqual(currentDeployment.deployment.scenario.bindings, { 'test-binding': '10', 'another-binding': '300' @@ -1487,47 +1494,42 @@ ${dotrain}`; }); it('should generate multicalldata for deposit and add order with existing vault ids', async () => { - // expressionDeployerAddress() call + await mockRainlangRegistryCalls(); await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); - // interpreterAddress() call + // I_STORE()() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); - // storeAddress() call + // I_PARSER() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); - // parserAddress() call - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); // parse2() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') // 0x1234 encoded bytes .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - await gui.setDeposit('token1', '1000'); - await gui.setDeposit('token2', '5000'); + await builder.setDeposit('token1', '1000'); + await builder.setDeposit('token2', '5000'); - gui.setFieldValue('test-binding', '0xbeef'); + builder.setFieldValue('test-binding', '0xbeef'); const calldata = extractWasmEncodedData( - await gui.generateDepositAndAddOrderCalldatas() + await builder.generateDepositAndAddOrderCalldatas() ); assert.equal(calldata.length, 3594); - let result = gui.getCurrentDeployment(); - const currentDeployment = extractWasmEncodedData(result); + let result = builder.getCurrentDeployment(); + const currentDeployment = extractWasmEncodedData(result); assert.deepEqual(currentDeployment.deployment.scenario.bindings, { 'test-binding': '0xbeef', 'another-binding': '300' @@ -1535,48 +1537,43 @@ ${dotrain}`; }); it('should generate multicalldata for deposit and add order with missing field value', async () => { - // expressionDeployerAddress() call + await mockRainlangRegistryCalls(); await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); - // interpreterAddress() call + // I_STORE()() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); - // storeAddress() call + // I_PARSER() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); - // parserAddress() call - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); // parse2() call await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') // 0x1234 encoded bytes .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - gui.unsetFieldValue('test-binding'); - assert.deepEqual(extractWasmEncodedData(gui.getAllFieldValues()), []); + builder.unsetFieldValue('test-binding'); + assert.deepEqual(extractWasmEncodedData(builder.getAllFieldValues()), []); - await gui.setDeposit('token1', '1000'); - await gui.setDeposit('token2', '5000'); + await builder.setDeposit('token1', '1000'); + await builder.setDeposit('token2', '5000'); const calldata = extractWasmEncodedData( - await gui.generateDepositAndAddOrderCalldatas() + await builder.generateDepositAndAddOrderCalldatas() ); assert.equal(calldata.length, 3658); - let result = gui.getCurrentDeployment(); - const currentDeployment = extractWasmEncodedData(result); + let result = builder.getCurrentDeployment(); + const currentDeployment = extractWasmEncodedData(result); assert.deepEqual(currentDeployment.deployment.scenario.bindings, { 'test-binding': '10', 'another-binding': '300' @@ -1599,52 +1596,49 @@ ${dotrain}`; '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - let testDotrain = `${guiConfig2} + let testDotrain = `${builderConfig2} ${dotrainWithoutVaultIds}`; - let result = await DotrainOrderGui.newWithDeployment( + let result = await RaindexOrderBuilder.newWithDeployment( testDotrain, undefined, 'other-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); + await mockRainlangRegistryCalls(); await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') // 0x1234 encoded bytes .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - await gui.setDeposit('token1', '1000'); - await gui.setDeposit('token2', '5000'); + await builder.setDeposit('token1', '1000'); + await builder.setDeposit('token2', '5000'); - gui.setFieldValue('test-binding', '0'); + builder.setFieldValue('test-binding', '0'); const calldata = extractWasmEncodedData( - await gui.generateDepositAndAddOrderCalldatas() + await builder.generateDepositAndAddOrderCalldatas() ); assert.equal(calldata.length, 3914); - const currentDeployment = extractWasmEncodedData( - gui.getCurrentDeployment() + const currentDeployment = extractWasmEncodedData( + builder.getCurrentDeployment() ); assert.equal( currentDeployment.deployment.order.inputs[0].vaultId, @@ -1654,36 +1648,36 @@ ${dotrainWithoutVaultIds}`; it('should throw error on order operations without selecting required tokens', async () => { let testDotrain = ` - ${guiConfig3} + ${builderConfig3} ${dotrainWithoutTokens} `; - let result = await DotrainOrderGui.newWithDeployment( + let result = await RaindexOrderBuilder.newWithDeployment( testDotrain, undefined, 'other-deployment' ); - const testGui = extractWasmEncodedData(result); + const testBuilder = extractWasmEncodedData(result); - let result1 = await testGui.checkAllowances('0x1234567890abcdef1234567890abcdef12345678'); + let result1 = await testBuilder.checkAllowances('0x1234567890abcdef1234567890abcdef12345678'); if (result1.error) { expect(result1.error.msg).toBe('Token must be selected: token1'); expect(result1.error.readableMsg).toBe("The token 'token1' must be selected to proceed."); } else expect.fail('Expected error'); - let result2 = await testGui.generateDepositCalldatas(); + let result2 = await testBuilder.generateDepositCalldatas(); if (result2.error) { expect(result2.error.msg).toBe('Token must be selected: token1'); expect(result2.error.readableMsg).toBe("The token 'token1' must be selected to proceed."); } else expect.fail('Expected error'); - let result3 = await testGui.generateAddOrderCalldata(); + let result3 = await testBuilder.generateAddOrderCalldata(); if (result3.error) { expect(result3.error.msg).toBe('Token must be selected: token1'); expect(result3.error.readableMsg).toBe("The token 'token1' must be selected to proceed."); } else expect.fail('Expected error'); - let result4 = await testGui.generateDepositAndAddOrderCalldatas(); + let result4 = await testBuilder.generateDepositAndAddOrderCalldatas(); if (result4.error) { expect(result4.error.msg).toBe('Token must be selected: token1'); expect(result4.error.readableMsg).toBe("The token 'token1' must be selected to proceed."); @@ -1706,8 +1700,8 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - let guiConfig = ` -gui: + let builderConfig = ` +builder: name: Test test description: Test test test deployments: @@ -1730,20 +1724,20 @@ gui: presets: - value: "0xbeef" `; - let testDotrain = `${guiConfig} + let testDotrain = `${builderConfig} ${dotrainWithoutVaultIds}`; - let result = await DotrainOrderGui.newWithDeployment( + let result = await RaindexOrderBuilder.newWithDeployment( testDotrain, undefined, 'other-deployment' ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); - await gui.setDeposit('token1', '1000'); - await gui.setDeposit('token2', '5000'); + await builder.setDeposit('token1', '1000'); + await builder.setDeposit('token2', '5000'); - let result1 = await gui.generateAddOrderCalldata(); + let result1 = await builder.generateAddOrderCalldata(); if (result1.error) { expect(result1.error.msg).toBe('Missing field value: Test binding'); expect(result1.error.readableMsg).toBe( @@ -1751,7 +1745,7 @@ ${dotrainWithoutVaultIds}`; ); } else expect.fail('Expected error'); - let result2 = await gui.generateDepositAndAddOrderCalldatas(); + let result2 = await builder.generateDepositAndAddOrderCalldatas(); if (result2.error) { expect(result2.error.msg).toBe('Missing field value: Test binding'); expect(result2.error.readableMsg).toBe( @@ -1759,13 +1753,13 @@ ${dotrainWithoutVaultIds}`; ); } else expect.fail('Expected error'); - let missingFieldValues = extractWasmEncodedData( - gui.getMissingFieldValues() + let missingFieldValues = extractWasmEncodedData( + builder.getMissingFieldValues() ); assert.equal(missingFieldValues.length, 1); assert.deepEqual( missingFieldValues[0], - extractWasmEncodedData(gui.getFieldDefinition('test-binding')) + extractWasmEncodedData(builder.getFieldDefinition('test-binding')) ); }); @@ -1779,47 +1773,47 @@ ${dotrainWithoutVaultIds}`; ); let testDotrain = ` - ${guiConfig2} + ${builderConfig2} ${dotrainWithoutVaultIds} `; - let guiResult = await DotrainOrderGui.newWithDeployment( + let builderResult = await RaindexOrderBuilder.newWithDeployment( testDotrain, undefined, 'other-deployment', stateUpdateCallback ); - const gui = extractWasmEncodedData(guiResult); + const builder = extractWasmEncodedData(builderResult); - const currentDeployment = extractWasmEncodedData( - gui.getCurrentDeployment() + const currentDeployment = extractWasmEncodedData( + builder.getCurrentDeployment() ); assert.equal(currentDeployment.deployment.order.inputs[0].vaultId, undefined); assert.equal(currentDeployment.deployment.order.outputs[0].vaultId, undefined); - assert.equal(extractWasmEncodedData(gui.hasAnyVaultId()), false); + assert.equal(extractWasmEncodedData(builder.hasAnyVaultId()), false); - gui.setVaultId('input', 'token1', '0x123'); + builder.setVaultId('input', 'token1', '0x123'); - assert.equal(extractWasmEncodedData(gui.hasAnyVaultId()), true); + assert.equal(extractWasmEncodedData(builder.hasAnyVaultId()), true); assert.equal( - extractWasmEncodedData>>(gui.getVaultIds()) + extractWasmEncodedData>>(builder.getVaultIds()) .get('input') ?.get('token1'), '0x123' ); assert.equal( - extractWasmEncodedData>>(gui.getVaultIds()) + extractWasmEncodedData>>(builder.getVaultIds()) .get('output') ?.get('token2'), undefined ); - gui.setVaultId('output', 'token2', '0x234'); + builder.setVaultId('output', 'token2', '0x234'); - const newCurrentDeployment = extractWasmEncodedData( - gui.getCurrentDeployment() + const newCurrentDeployment = extractWasmEncodedData( + builder.getCurrentDeployment() ); assert.notEqual(newCurrentDeployment.deployment.order.inputs[0].vaultId, undefined); assert.notEqual(newCurrentDeployment.deployment.order.outputs[0].vaultId, undefined); @@ -1827,28 +1821,28 @@ ${dotrainWithoutVaultIds}`; assert.equal(newCurrentDeployment.deployment.order.outputs[0].vaultId, '0x234'); const vaultIds = extractWasmEncodedData>>( - gui.getVaultIds() + builder.getVaultIds() ); assert.equal(vaultIds.get('input')?.get('token1'), '0x123'); assert.equal(vaultIds.get('output')?.get('token2'), '0x234'); - gui.setVaultId('input', 'token1', undefined); + builder.setVaultId('input', 'token1', undefined); assert.equal( - extractWasmEncodedData>>(gui.getVaultIds()) + extractWasmEncodedData>>(builder.getVaultIds()) .get('input') ?.get('token1'), undefined ); - gui.setVaultId('output', 'token2', ''); + builder.setVaultId('output', 'token2', ''); assert.equal( - extractWasmEncodedData>>(gui.getVaultIds()) + extractWasmEncodedData>>(builder.getVaultIds()) .get('output') ?.get('token2'), undefined ); - const result = gui.setVaultId('input', 'token1', 'test'); + const result = builder.setVaultId('input', 'token1', 'test'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe( "Invalid value for field 'vault-id': Failed to parse vault id: digit 29 is out of range for base 10 in token 'token1' in inputs of order 'some-order'" @@ -1862,7 +1856,7 @@ ${dotrainWithoutVaultIds}`; assert.equal(stateUpdateCallback.mock.calls.length, 4); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); @@ -1884,10 +1878,10 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000754656b656e203200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025432000000000000000000000000000000000000000000000000000000000000' ); - await gui.setDeposit('token1', '0'); - await gui.setDeposit('token2', '0'); + await builder.setDeposit('token1', '0'); + await builder.setDeposit('token2', '0'); const calldatas = extractWasmEncodedData( - await gui.generateDepositCalldatas() + await builder.generateDepositCalldatas() ); // @ts-expect-error - valid result assert.equal(calldatas.Calldatas.length, 0); @@ -1932,32 +1926,28 @@ ${dotrainWithoutVaultIds}`; ); await mockServer .forPost('/rpc-url') - .withBodyIncluding(EXPRESSION_DEPLOYER_ADDRESS_SELECTOR) + .withBodyIncluding('0x56fb83e9') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '1'.repeat(40)}`); await mockServer .forPost('/rpc-url') - .withBodyIncluding(INTERPRETER_ADDRESS_SELECTOR) + .withBodyIncluding('0x251ac32e') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '2'.repeat(40)}`); await mockServer .forPost('/rpc-url') - .withBodyIncluding(STORE_ADDRESS_SELECTOR) + .withBodyIncluding('0xf79693f4') .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '3'.repeat(40)}`); await mockServer .forPost('/rpc-url') - .withBodyIncluding(PARSER_ADDRESS_SELECTOR) - .thenSendJsonRpcResult(`0x${'0'.repeat(24) + '4'.repeat(40)}`); - await mockServer - .forPost('/rpc-url') - .withBodyIncluding(PARSE2_SELECTOR) + .withBodyIncluding('0xa3869e14') .thenSendJsonRpcResult( '0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000' ); - await gui.setDeposit('token2', '5000'); - gui.setFieldValue('test-binding', '10'); + await builder.setDeposit('token2', '5000'); + builder.setFieldValue('test-binding', '10'); let result = extractWasmEncodedData( - await gui.getDeploymentTransactionArgs('0x1234567890abcdef1234567890abcdef12345678') + await builder.getDeploymentTransactionArgs('0x1234567890abcdef1234567890abcdef12345678') ); assert.equal(result.approvals.length, 1); @@ -1985,9 +1975,9 @@ ${dotrainWithoutVaultIds}`; const metaText = new TextDecoder().decode(hexToBytes(metaBytes).slice(8)); assert.ok(metaText.includes('\n#handle-add-order')); - gui.unsetDeposit('token2'); + builder.unsetDeposit('token2'); result = extractWasmEncodedData( - await gui.getDeploymentTransactionArgs('0x1234567890abcdef1234567890abcdef12345678') + await builder.getDeploymentTransactionArgs('0x1234567890abcdef1234567890abcdef12345678') ); assert.equal(result.approvals.length, 0); @@ -2016,27 +2006,29 @@ ${dotrainWithoutVaultIds}`; }); describe('select tokens tests', async () => { - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; let stateUpdateCallback: Mock; beforeEach(async () => { stateUpdateCallback = vi.fn(); let dotrain3 = ` - ${guiConfig3} + ${builderConfig3} ${dotrainWithoutTokens} `; - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrain3, undefined, 'other-deployment', stateUpdateCallback ); - gui = extractWasmEncodedData(result); + builder = extractWasmEncodedData(result); }); it('should get select tokens', async () => { - const selectTokens = extractWasmEncodedData(gui.getSelectTokens()); + const selectTokens = extractWasmEncodedData( + builder.getSelectTokens() + ); assert.equal(selectTokens.length, 2); assert.equal(selectTokens[0].key, 'token1'); assert.equal(selectTokens[1].key, 'token2'); @@ -2050,19 +2042,19 @@ ${dotrainWithoutVaultIds}`; .thenSendJsonRpcResult( '0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000' ); - let guiResult = await DotrainOrderGui.newWithDeployment( - dotrainWithGui, + let builderResult = await RaindexOrderBuilder.newWithDeployment( + dotrainWithBuilder, undefined, 'some-deployment' ); - const testGui = extractWasmEncodedData(guiResult); + const testBuilder = extractWasmEncodedData(builderResult); assert.equal( - extractWasmEncodedData(testGui.getSelectTokens()).length, + extractWasmEncodedData(testBuilder.getSelectTokens()).length, 0 ); - const result = await testGui.setSelectToken('token1', '0x1'); + const result = await testBuilder.setSelectToken('token1', '0x1'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Select tokens not set'); expect(result.error.readableMsg).toBe( @@ -2071,7 +2063,7 @@ ${dotrainWithoutVaultIds}`; }); it('should throw error if token not found', async () => { - const result = await gui.setSelectToken('token3', '0x1'); + const result = await builder.setSelectToken('token3', '0x1'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe('Token not found token3'); expect(result.error.readableMsg).toBe( @@ -2094,38 +2086,38 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000015db63900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a54657468657220555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553445400000000000000000000000000000000000000000000000000000000' ); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), false); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token2')), false); - assert.equal(extractWasmEncodedData(gui.areAllTokensSelected()), false); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), false); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token2')), false); + assert.equal(extractWasmEncodedData(builder.areAllTokensSelected()), false); - let result = await gui.getTokenInfo('token1'); + let result = await builder.getTokenInfo('token1'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe("Missing required field 'tokens' in root"); expect(result.error.readableMsg).toBe( "YAML configuration error: Missing required field 'tokens' in root" ); - result = await gui.getTokenInfo('token2'); + result = await builder.getTokenInfo('token2'); if (!result.error) expect.fail('Expected error'); expect(result.error.msg).toBe("Missing required field 'tokens' in root"); expect(result.error.readableMsg).toBe( "YAML configuration error: Missing required field 'tokens' in root" ); - await gui.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); - await gui.setSelectToken('token2', '0x8888888888888888888888888888888888888888'); + await builder.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); + await builder.setSelectToken('token2', '0x8888888888888888888888888888888888888888'); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), true); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token2')), true); - assert.equal(extractWasmEncodedData(gui.areAllTokensSelected()), true); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), true); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token2')), true); + assert.equal(extractWasmEncodedData(builder.areAllTokensSelected()), true); - result = await gui.getTokenInfo('token1'); + result = await builder.getTokenInfo('token1'); const tokenInfo = extractWasmEncodedData(result); assert.equal(tokenInfo.name, 'Tether USD'); assert.equal(tokenInfo.symbol, 'USDT'); assert.equal(tokenInfo.decimals, 6); - result = await gui.getTokenInfo('token2'); + result = await builder.getTokenInfo('token2'); const tokenInfo2 = extractWasmEncodedData(result); assert.equal(tokenInfo2.name, 'Tether USD'); assert.equal(tokenInfo2.symbol, 'USDT'); @@ -2133,7 +2125,7 @@ ${dotrainWithoutVaultIds}`; assert.equal(stateUpdateCallback.mock.calls.length, 2); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); @@ -2151,19 +2143,19 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000015db63900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a54657468657220555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553445400000000000000000000000000000000000000000000000000000000' ); - await gui.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), true); + await builder.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), true); - let result = await gui.getTokenInfo('token1'); + let result = await builder.getTokenInfo('token1'); const tokenInfo1 = extractWasmEncodedData(result); assert.equal(tokenInfo1.name, 'Tether USD'); assert.equal(tokenInfo1.symbol, 'USDT'); assert.equal(tokenInfo1.decimals, 6); - await gui.setSelectToken('token1', '0x8888888888888888888888888888888888888888'); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), true); + await builder.setSelectToken('token1', '0x8888888888888888888888888888888888888888'); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), true); - result = await gui.getTokenInfo('token1'); + result = await builder.getTokenInfo('token1'); const tokenInfo2 = extractWasmEncodedData(result); assert.equal(tokenInfo2.name, 'Tether USD'); assert.equal(tokenInfo2.symbol, 'USDT'); @@ -2171,24 +2163,24 @@ ${dotrainWithoutVaultIds}`; assert.equal(stateUpdateCallback.mock.calls.length, 2); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); it('should remove select token', async () => { stateUpdateCallback = vi.fn(); let dotrain3 = ` - ${guiConfig3} + ${builderConfig3} ${dotrainWithoutTokens} `; - let result = await DotrainOrderGui.newWithDeployment( + let result = await RaindexOrderBuilder.newWithDeployment( dotrain3, undefined, 'other-deployment', stateUpdateCallback ); - const gui = extractWasmEncodedData(result); + const builder = extractWasmEncodedData(result); mockServer .forPost('/rpc-url') @@ -2203,18 +2195,18 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000015db63900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a54657468657220555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553445400000000000000000000000000000000000000000000000000000000' ); - await gui.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), true); + await builder.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), true); - const tokenInfo = extractWasmEncodedData(await gui.getTokenInfo('token1')); + const tokenInfo = extractWasmEncodedData(await builder.getTokenInfo('token1')); assert.equal(tokenInfo.name, 'Tether USD'); assert.equal(tokenInfo.symbol, 'USDT'); assert.equal(tokenInfo.decimals, 6); - gui.unsetSelectToken('token1'); - assert.equal(extractWasmEncodedData(gui.isSelectTokenSet('token1')), false); + builder.unsetSelectToken('token1'); + assert.equal(extractWasmEncodedData(builder.isSelectTokenSet('token1')), false); - let result1 = await gui.getTokenInfo('token1'); + let result1 = await builder.getTokenInfo('token1'); if (!result1.error) expect.fail('Expected error'); expect(result1.error.msg).toBe("Missing required field 'tokens' in root"); expect(result1.error.readableMsg).toBe( @@ -2223,7 +2215,7 @@ ${dotrainWithoutVaultIds}`; assert.equal(stateUpdateCallback.mock.calls.length, 2); expect(stateUpdateCallback).toHaveBeenCalledWith( - extractWasmEncodedData(gui.serializeState()) + extractWasmEncodedData(builder.serializeState()) ); }); @@ -2241,10 +2233,10 @@ ${dotrainWithoutVaultIds}`; '0x00000000000000000000000000000000000000000000000000000000015db63900000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a54657468657220555344000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000045553445400000000000000000000000000000000000000000000000000000000' ); - await gui.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); - await gui.setSelectToken('token2', '0x8888888888888888888888888888888888888888'); + await builder.setSelectToken('token1', '0x6666666666666666666666666666666666666666'); + await builder.setSelectToken('token2', '0x8888888888888888888888888888888888888888'); - const allTokens = extractWasmEncodedData(await gui.getAllTokens()); + const allTokens = extractWasmEncodedData(await builder.getAllTokens()); assert.equal(allTokens.length, 2); assert.equal(allTokens[0].address, '0x6666666666666666666666666666666666666666'); assert.equal(allTokens[0].name, 'Tether USD'); @@ -2275,7 +2267,7 @@ ${dotrainWithoutVaultIds}`; ); const result = extractWasmEncodedData( - await gui.getAccountBalance( + await builder.getAccountBalance( '0xc2132d05d31c914a87c6611c10748aeb04b58e8f', // Use token1's address from YAML '0x1234567890abcdef1234567890abcdef12345678' ) @@ -2322,13 +2314,13 @@ ${dotrainWithoutVaultIds}`; logoURI: 'http://localhost.com' }); - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrainForRemotes, undefined, 'test-deployment' ); - const gui = extractWasmEncodedData(result); - assert.ok(gui.getCurrentDeployment()); + const builder = extractWasmEncodedData(result); + assert.ok(builder.getCurrentDeployment()); }); it('should fail for same remote network key in response', async () => { @@ -2366,7 +2358,7 @@ ${dotrainWithoutVaultIds}`; } ]); - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrainForRemotes, undefined, 'test-deployment' @@ -2430,7 +2422,7 @@ ${dotrainWithoutVaultIds}`; logoURI: 'http://localhost.com' }); - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrainForRemotes, undefined, 'test-deployment' @@ -2444,7 +2436,7 @@ ${dotrainWithoutVaultIds}`; }); describe('remote tokens tests', () => { - let gui: DotrainOrderGui; + let builder: RaindexOrderBuilder; it('should fetch remote tokens', async () => { mockServer @@ -2490,13 +2482,13 @@ ${dotrainWithoutVaultIds}`; logoURI: 'http://localhost.com' }); - const result = await DotrainOrderGui.newWithDeployment( + const result = await RaindexOrderBuilder.newWithDeployment( dotrainForRemotes, undefined, 'other-deployment' ); - const gui = extractWasmEncodedData(result); - assert.ok(gui.getCurrentDeployment()); + const builder = extractWasmEncodedData(result); + assert.ok(builder.getCurrentDeployment()); }); }); }); diff --git a/packages/orderbook/test/js_api/dotrainRegistry.test.ts b/packages/orderbook/test/js_api/dotrainRegistry.test.ts index 18782b1f7b..9a43cd13de 100644 --- a/packages/orderbook/test/js_api/dotrainRegistry.test.ts +++ b/packages/orderbook/test/js_api/dotrainRegistry.test.ts @@ -72,8 +72,8 @@ tokens: const MOCK_DOTRAIN_PREFIX = ` version: ${SPEC_VERSION} -gui: - name: Test gui +builder: + name: Test builder description: Test description short-description: Test short description deployments: @@ -279,7 +279,7 @@ auction-dca http://localhost:8231/auction-dca.rain`; const fixedLimitDetails = orderDetails.valid.get('fixed-limit'); assert(fixedLimitDetails); - assert.strictEqual(fixedLimitDetails.name, 'Test gui'); + assert.strictEqual(fixedLimitDetails.name, 'Test builder'); assert.strictEqual(fixedLimitDetails.description, 'Test description'); assert.strictEqual(fixedLimitDetails.short_description, 'Test short description'); }); @@ -334,7 +334,7 @@ invalid-order http://localhost:8231/invalid.rain`; }); }); - describe('DotrainRegistry GUI Creation', () => { + describe('DotrainRegistry Builder Creation', () => { let registry: DotrainRegistry; beforeEach(async () => { @@ -350,47 +350,51 @@ fixed-limit http://localhost:8231/fixed-limit.rain`; ); }); - it('should create GUI for valid order and deployment', async () => { - const gui = extractWasmEncodedData(await registry.getGui('fixed-limit', 'flare', null, null)); + it('should create builder for valid order and deployment', async () => { + const builder = extractWasmEncodedData( + await registry.getOrderBuilder('fixed-limit', 'flare', null, null) + ); - const currentDeployment = extractWasmEncodedData(gui.getCurrentDeployment()); + const currentDeployment = extractWasmEncodedData(builder.getCurrentDeployment()); assert.strictEqual(currentDeployment.name, 'Flare order name'); assert.strictEqual(currentDeployment.description, 'Flare order description'); }); - it('should create GUI with state update callback', async () => { + it('should create builder with state update callback', async () => { const stateCallback = () => {}; - const gui = extractWasmEncodedData( - await registry.getGui('fixed-limit', 'base', null, stateCallback) + const builder = extractWasmEncodedData( + await registry.getOrderBuilder('fixed-limit', 'base', null, stateCallback) ); - const currentDeployment = extractWasmEncodedData(gui.getCurrentDeployment()); + const currentDeployment = extractWasmEncodedData(builder.getCurrentDeployment()); assert.strictEqual(currentDeployment.name, 'Base order name'); assert.strictEqual(currentDeployment.description, 'Base order description'); }); - it('should restore GUI from serialized state when provided', async () => { - let gui = extractWasmEncodedData(await registry.getGui('fixed-limit', 'flare', null, null)); + it('should restore builder from serialized state when provided', async () => { + let builder = extractWasmEncodedData( + await registry.getOrderBuilder('fixed-limit', 'flare', null, null) + ); - gui.setFieldValue('test-binding', '42'); - const serializedState = extractWasmEncodedData(gui.serializeState()); + builder.setFieldValue('test-binding', '42'); + const serializedState = extractWasmEncodedData(builder.serializeState()); - gui = extractWasmEncodedData( - await registry.getGui('fixed-limit', 'flare', serializedState, null) + builder = extractWasmEncodedData( + await registry.getOrderBuilder('fixed-limit', 'flare', serializedState, null) ); const fieldValue = extractWasmEncodedData<{ value: string }>( - gui.getFieldValue('test-binding') + builder.getFieldValue('test-binding') ); assert.strictEqual(fieldValue.value, '42'); }); - it('should handle GUI creation for non-existent order', async () => { - const result = await registry.getGui('non-existent', 'flare', null, null); + it('should handle builder creation for non-existent order', async () => { + const result = await registry.getOrderBuilder('non-existent', 'flare', null, null); assert(result.error); assert(result.error.readableMsg.includes("order key 'non-existent' was not found")); }); @@ -432,7 +436,7 @@ registrys: `; const MOCK_DOTRAIN_SIMPLE = ` -gui: +builder: name: Test Order description: Test description deployments: diff --git a/packages/ui-components/ARCHITECTURE.md b/packages/ui-components/ARCHITECTURE.md index 309f9ec228..4477665050 100644 --- a/packages/ui-components/ARCHITECTURE.md +++ b/packages/ui-components/ARCHITECTURE.md @@ -6,7 +6,7 @@ This package is the reusable Svelte component library for building Rain Orderboo ## Overview - Purpose - - Provide a cohesive, app‑ready set of Svelte components and helpers for Orderbook features: tables, detail views, charts, deployment GUI, toasts, and transaction UX. + - Provide a cohesive, app‑ready set of Svelte components and helpers for Orderbook features: tables, detail views, charts, deployment builder, toasts, and transaction UX. - Ship provider and hook contexts so apps can wire wallet, client, registry, toasts, and transaction state consistently. - Targets - Svelte 4 components, packaged with `svelte-package` for consumption in SvelteKit/Vite apps. @@ -44,9 +44,9 @@ The library exposes lightweight provider components that set Svelte contexts, pl - Toasts - `ToastProvider` — provides a toasts store and renders `ToastDetail` instances. - `useToasts()` — returns `{ toasts, addToast, removeToast, errToast }`. -- GUI (deploy flows) - - `GuiProvider` — provides a `DotrainOrderGui` instance for deployment flows. - - `useGui()` — retrieves the GUI instance; integrates with deployment components. +- Builder (deploy flows) + - `RaindexOrderBuilderProvider` — provides a `RaindexOrderBuilder` instance for deployment flows. + - `useRaindexOrderBuilder()` — retrieves the builder instance; integrates with deployment components. Notes @@ -62,7 +62,7 @@ Notes - `OrderDetail`, `VaultDetail`, `TanstackOrderQuote`, `TanstackPageContentDetail`, `Hash`, `OrderOrVaultHash`. - Charts - `LightweightChart`, `TanstackLightweightChartLine`, `OrderTradesChart`, `VaultBalanceChart` with time helpers and themes. -- Deployment GUI +- Deployment Builder - `OrderPage`, `DeploymentsSection`, `DeploymentSteps`, `TokenIOInput`, `FieldDefinitionInput`, `SelectToken`, `DisclaimerModal`, `ValidOrdersSection`, `InvalidOrdersSection`. - Services: dotrain registry fetch/validate/share (`registry.ts`, `loadRegistryUrl.ts`, `handleShareChoices.ts`). - Wallet UX & general UI @@ -91,8 +91,8 @@ Notes `src/lib/index.ts` re‑exports by category: - Components: tables, detail views, charts, editors, inputs, icons, wallet/connectivity, UI primitives. -- Providers: `GuiProvider`, `RaindexClientProvider`, `WalletProvider`, `RegistryProvider`, `ToastProvider`, `TransactionProvider`. -- Hooks: `useGui`, `useRaindexClient`, `useAccount`, `useRegistry`, `useToasts`, `useTransactions`. +- Providers: `RaindexOrderBuilderProvider`, `RaindexClientProvider`, `WalletProvider`, `RegistryProvider`, `ToastProvider`, `TransactionProvider`. +- Hooks: `useRaindexOrderBuilder`, `useRaindexClient`, `useAccount`, `useRegistry`, `useToasts`, `useTransactions`. - Types: app stores, modal/transaction/toast/order typings. - Functions: time helpers, explorer link, TanStack invalidation helpers, mocks for tests. - Constants: query keys, default page sizes/intervals, chart/code editor themes. @@ -104,7 +104,7 @@ Notes ## Directory Layout - `src/lib/components/` — Svelte components grouped by domain (`tables`, `detail`, `charts`, `deployment`, `transactions`, `wallet`, `input`, etc.). -- `src/lib/providers/` — Context providers for GUI, client, wallet, registry, toasts, transactions. +- `src/lib/providers/` — Context providers for builder, client, wallet, registry, toasts, transactions. - `src/lib/hooks/` — Accessors for provider contexts. - `src/lib/models/` — State models such as `TransactionStore`. - `src/lib/types/` — Type helpers and enums. @@ -161,7 +161,7 @@ export default { - Ensure the Query Client is available in context before mounting `TransactionProvider`. - Pass a valid `wagmi` `Config` to `TransactionProvider` and ensure wallet connectors are initialized at the app level. - Include this package in Tailwind `content` globs to avoid missing styles. -- For deployment flows, pass a `DotrainOrderGui` via `GuiProvider` and use the registry helpers to load/validate dotrain entries. +- For deployment flows, pass a `RaindexOrderBuilder` via `RaindexOrderBuilderProvider` and use the registry helpers to load/validate dotrain entries. This document explains what `packages/ui-components` is for, how providers and components are organized, how to build and test the package, and how it integrates with the rest of the Rain Orderbook workspace. diff --git a/packages/ui-components/src/__tests__/ComposedRainlangModal.test.ts b/packages/ui-components/src/__tests__/ComposedRainlangModal.test.ts index f281496fc9..2a5e275191 100644 --- a/packages/ui-components/src/__tests__/ComposedRainlangModal.test.ts +++ b/packages/ui-components/src/__tests__/ComposedRainlangModal.test.ts @@ -1,26 +1,26 @@ import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { render, fireEvent, waitFor } from '@testing-library/svelte'; import ComposedRainlangModal from '../lib/components/deployment/ComposedRainlangModal.svelte'; -import type { DotrainOrderGui } from '@rainlanguage/orderbook'; -import { useGui } from '$lib/hooks/useGui'; +import type { RaindexOrderBuilder } from '@rainlanguage/orderbook'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; vi.mock('svelte-codemirror-editor', async () => { const mockCodeMirror = (await import('../lib/__mocks__/MockComponent.svelte')).default; return { default: mockCodeMirror }; }); -vi.mock('$lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('$lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); -const mockGui = { +const mockBuilder = { getComposedRainlang: vi.fn(() => Promise.resolve('mocked rainlang text')) -} as unknown as DotrainOrderGui; +} as unknown as RaindexOrderBuilder; describe('ComposedRainlangModal', () => { beforeEach(() => { vi.clearAllMocks(); - (useGui as Mock).mockReturnValue(mockGui); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilder); }); it('should open modal and display rainlang text when button is clicked', async () => { @@ -30,7 +30,7 @@ describe('ComposedRainlangModal', () => { await fireEvent.click(button); await waitFor(() => { - expect(mockGui.getComposedRainlang).toHaveBeenCalled(); + expect(mockBuilder.getComposedRainlang).toHaveBeenCalled(); expect(getByTestId('modal')).toBeInTheDocument(); }); }); diff --git a/packages/ui-components/src/__tests__/DeploymentSteps.test.ts b/packages/ui-components/src/__tests__/DeploymentSteps.test.ts index 9b60495838..e863cf6151 100644 --- a/packages/ui-components/src/__tests__/DeploymentSteps.test.ts +++ b/packages/ui-components/src/__tests__/DeploymentSteps.test.ts @@ -1,25 +1,25 @@ import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { render, screen, waitFor } from '@testing-library/svelte'; import DeploymentSteps from '../lib/components/deployment/DeploymentSteps.svelte'; -import { DotrainOrderGui, type ScenarioCfg } from '@rainlanguage/orderbook'; +import { RaindexOrderBuilder, type ScenarioCfg } from '@rainlanguage/orderbook'; import type { ComponentProps } from 'svelte'; import { readable, writable } from 'svelte/store'; import type { AppKit } from '@reown/appkit'; -import type { GuiDeploymentCfg, RaindexClient } from '@rainlanguage/orderbook'; +import type { OrderBuilderDeploymentCfg, RaindexClient } from '@rainlanguage/orderbook'; import userEvent from '@testing-library/user-event'; -import { useGui } from '$lib/hooks/useGui'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; import { useAccount } from '$lib/providers/wallet/useAccount'; import type { Account } from '$lib/types/account'; import { useRaindexClient } from '$lib/hooks/useRaindexClient'; vi.mock('@rainlanguage/orderbook', () => ({ - DotrainOrderGui: vi.fn() + RaindexOrderBuilder: vi.fn() })); const { mockConnectedStore } = await vi.hoisted(() => import('../lib/__mocks__/stores')); -vi.mock('$lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('$lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); vi.mock('$lib/hooks/useRaindexClient', () => ({ @@ -79,7 +79,7 @@ const mockDeployment = { outputs: [] } } -} as unknown as GuiDeploymentCfg; +} as unknown as OrderBuilderDeploymentCfg; const mockOnDeploy = vi.fn(); @@ -97,14 +97,14 @@ const defaultProps: DeploymentStepsProps = { } as DeploymentStepsProps; describe('DeploymentSteps', () => { - let guiInstance: DotrainOrderGui; - let mockGui: DotrainOrderGui; + let builderInstance: RaindexOrderBuilder; + let mockBuilder: RaindexOrderBuilder; beforeEach(() => { vi.clearAllMocks(); // Create a mock instance with all the methods - guiInstance = { + builderInstance = { areAllTokensSelected: vi.fn().mockReturnValue({ value: false }), getSelectTokens: vi.fn().mockReturnValue({ value: [] }), getCurrentDeployment: vi.fn().mockReturnValue(mockDeployment), @@ -151,10 +151,10 @@ describe('DeploymentSteps', () => { getAccountBalance: vi.fn().mockResolvedValue({ value: '1000000000000000000' }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - mockGui = guiInstance; - vi.mocked(useGui).mockReturnValue(mockGui); + mockBuilder = builderInstance; + vi.mocked(useRaindexOrderBuilder).mockReturnValue(mockBuilder); // Set default mock return value for useAccount vi.mocked(useAccount).mockReturnValue({ @@ -172,7 +172,7 @@ describe('DeploymentSteps', () => { }); it('shows select tokens section when tokens need to be selected', async () => { - (mockGui.getSelectTokens as Mock).mockReturnValue({ + (mockBuilder.getSelectTokens as Mock).mockReturnValue({ value: ['token1', 'token2'] }); @@ -195,14 +195,14 @@ describe('DeploymentSteps', () => { ]; // Set up specific mocks for this test - (mockGui.getSelectTokens as Mock).mockReturnValue({ + (mockBuilder.getSelectTokens as Mock).mockReturnValue({ value: mockSelectTokens }); - (mockGui.getTokenInfo as Mock).mockImplementation(() => {}); - (mockGui.areAllTokensSelected as Mock).mockReturnValue({ value: true }); - (mockGui.isSelectTokenSet as Mock).mockReturnValue({ value: false }); - (mockGui.setSelectToken as Mock).mockImplementation(() => {}); - (mockGui.getCurrentDeployment as Mock).mockReturnValue({ + (mockBuilder.getTokenInfo as Mock).mockImplementation(() => {}); + (mockBuilder.areAllTokensSelected as Mock).mockReturnValue({ value: true }); + (mockBuilder.isSelectTokenSet as Mock).mockReturnValue({ value: false }); + (mockBuilder.setSelectToken as Mock).mockImplementation(() => {}); + (mockBuilder.getCurrentDeployment as Mock).mockReturnValue({ value: { deployment: { order: { @@ -214,7 +214,7 @@ describe('DeploymentSteps', () => { } }); - (mockGui.getAllTokenInfos as Mock).mockResolvedValue({ + (mockBuilder.getAllTokenInfos as Mock).mockResolvedValue({ value: [ { address: '0x1', @@ -245,14 +245,14 @@ describe('DeploymentSteps', () => { ]; // Set up specific mocks for this test - (mockGui.getSelectTokens as Mock).mockReturnValue({ + (mockBuilder.getSelectTokens as Mock).mockReturnValue({ value: mockSelectTokens }); - (mockGui.getTokenInfo as Mock).mockImplementation(() => {}); - (mockGui.areAllTokensSelected as Mock).mockReturnValue({ value: true }); - (mockGui.isSelectTokenSet as Mock).mockReturnValue({ value: false }); - (mockGui.setSelectToken as Mock).mockImplementation(() => {}); - (mockGui.getCurrentDeployment as Mock).mockReturnValue({ + (mockBuilder.getTokenInfo as Mock).mockImplementation(() => {}); + (mockBuilder.areAllTokensSelected as Mock).mockReturnValue({ value: true }); + (mockBuilder.isSelectTokenSet as Mock).mockReturnValue({ value: false }); + (mockBuilder.setSelectToken as Mock).mockImplementation(() => {}); + (mockBuilder.getCurrentDeployment as Mock).mockReturnValue({ value: { deployment: { order: { @@ -264,7 +264,7 @@ describe('DeploymentSteps', () => { } }); - (mockGui.getAllTokenInfos as Mock).mockResolvedValue({ + (mockBuilder.getAllTokenInfos as Mock).mockResolvedValue({ value: [ { address: '0x1', @@ -294,14 +294,14 @@ describe('DeploymentSteps', () => { ]; // Set up specific mocks for this test - (mockGui.getSelectTokens as Mock).mockReturnValue({ + (mockBuilder.getSelectTokens as Mock).mockReturnValue({ value: mockSelectTokens }); - (mockGui.getTokenInfo as Mock).mockImplementation(() => {}); - (mockGui.areAllTokensSelected as Mock).mockReturnValue({ value: true }); - (mockGui.isSelectTokenSet as Mock).mockReturnValue({ value: false }); - (mockGui.setSelectToken as Mock).mockImplementation(() => {}); - (mockGui.getCurrentDeployment as Mock).mockReturnValue({ + (mockBuilder.getTokenInfo as Mock).mockImplementation(() => {}); + (mockBuilder.areAllTokensSelected as Mock).mockReturnValue({ value: true }); + (mockBuilder.isSelectTokenSet as Mock).mockReturnValue({ value: false }); + (mockBuilder.setSelectToken as Mock).mockImplementation(() => {}); + (mockBuilder.getCurrentDeployment as Mock).mockReturnValue({ value: { deployment: { order: { @@ -313,7 +313,7 @@ describe('DeploymentSteps', () => { } }); - (mockGui.getAllTokenInfos as Mock).mockResolvedValue({ + (mockBuilder.getAllTokenInfos as Mock).mockResolvedValue({ value: [ { address: '0x1', @@ -337,7 +337,7 @@ describe('DeploymentSteps', () => { }); await waitFor(() => { - expect(mockGui.areAllTokensSelected).toHaveBeenCalled(); + expect(mockBuilder.areAllTokensSelected).toHaveBeenCalled(); }); await waitFor(() => { @@ -347,7 +347,7 @@ describe('DeploymentSteps', () => { }); const selectTokenInput = screen.getByText('Token 1'); - (mockGui.getTokenInfo as Mock).mockResolvedValue({ + (mockBuilder.getTokenInfo as Mock).mockResolvedValue({ value: { address: '0x1', decimals: 18, @@ -358,7 +358,7 @@ describe('DeploymentSteps', () => { await user.type(selectTokenInput, '0x1'); const selectTokenOutput = screen.getByText('Token 2'); - (mockGui.getTokenInfo as Mock).mockResolvedValue({ + (mockBuilder.getTokenInfo as Mock).mockResolvedValue({ value: { address: '0x2', decimals: 18, @@ -369,14 +369,14 @@ describe('DeploymentSteps', () => { await user.type(selectTokenOutput, '0x2'); await waitFor(() => { - expect(mockGui.getAllTokenInfos).toHaveBeenCalled(); + expect(mockBuilder.getAllTokenInfos).toHaveBeenCalled(); }); const customAddressButtons = screen.getAllByTestId('custom-mode-button'); await user.click(customAddressButtons[customAddressButtons.length - 1]); const customInputs = screen.getAllByPlaceholderText('Enter token address (0x...)'); const lastCustomInput = customInputs[customInputs.length - 1]; - (mockGui.getTokenInfo as Mock).mockResolvedValue({ + (mockBuilder.getTokenInfo as Mock).mockResolvedValue({ value: { address: '0x3', decimals: 18, @@ -386,7 +386,7 @@ describe('DeploymentSteps', () => { }); await user.type(lastCustomInput, '0x3'); - (mockGui.getAllTokenInfos as Mock).mockResolvedValue({ + (mockBuilder.getAllTokenInfos as Mock).mockResolvedValue({ value: [ { address: '0x3', @@ -404,14 +404,14 @@ describe('DeploymentSteps', () => { }); await waitFor(() => { - expect(mockGui.getAllTokenInfos).toHaveBeenCalled(); + expect(mockBuilder.getAllTokenInfos).toHaveBeenCalled(); }); }); it('passes correct arguments to onDeploy prop', async () => { // Override the mock for this test - guiInstance.areAllTokensSelected = vi.fn().mockReturnValue({ value: true }); - vi.mocked(useGui).mockReturnValue(guiInstance); + builderInstance.areAllTokensSelected = vi.fn().mockReturnValue({ value: true }); + vi.mocked(useRaindexOrderBuilder).mockReturnValue(builderInstance); const mockRaindexClient = {} as unknown as RaindexClient; vi.mocked(useRaindexClient).mockReturnValue(mockRaindexClient); @@ -430,9 +430,9 @@ describe('DeploymentSteps', () => { await waitFor(() => { expect(mockOnDeploy).toHaveBeenCalledTimes(1); - const [raindexClient, guiArg] = mockOnDeploy.mock.calls[0]; + const [raindexClient, builderArg] = mockOnDeploy.mock.calls[0]; - expect(guiArg).toBe(mockGui); + expect(builderArg).toBe(mockBuilder); expect(raindexClient).toBe(mockRaindexClient); }); }); @@ -443,7 +443,7 @@ describe('DeploymentSteps', () => { { key: 'token2', name: 'Token 2', description: undefined } ]; - (mockGui.getSelectTokens as Mock).mockReturnValue({ + (mockBuilder.getSelectTokens as Mock).mockReturnValue({ value: mockSelectTokens }); @@ -457,26 +457,26 @@ describe('DeploymentSteps', () => { render(DeploymentSteps, { props: propsWithAccountStore }); await waitFor(() => { - expect(mockGui.getSelectTokens).toHaveBeenCalled(); + expect(mockBuilder.getSelectTokens).toHaveBeenCalled(); }); vi.clearAllMocks(); accountStore.set('0x456'); await waitFor(() => { - expect(mockGui.getTokenInfo).toHaveBeenCalledTimes(2); - expect(mockGui.getTokenInfo).toHaveBeenCalledWith('token1'); - expect(mockGui.getTokenInfo).toHaveBeenCalledWith('token2'); + expect(mockBuilder.getTokenInfo).toHaveBeenCalledTimes(2); + expect(mockBuilder.getTokenInfo).toHaveBeenCalledWith('token1'); + expect(mockBuilder.getTokenInfo).toHaveBeenCalledWith('token2'); }); await waitFor(() => { - expect(mockGui.getAccountBalance).toHaveBeenCalledTimes(2); + expect(mockBuilder.getAccountBalance).toHaveBeenCalledTimes(2); }); }); it('clears token balances when account becomes null', async () => { const mockSelectTokens = [{ key: 'token1', name: 'Token 1', description: undefined }]; - (mockGui.getSelectTokens as Mock).mockReturnValue({ + (mockBuilder.getSelectTokens as Mock).mockReturnValue({ value: mockSelectTokens }); @@ -490,7 +490,7 @@ describe('DeploymentSteps', () => { render(DeploymentSteps, { props: propsWithAccountStore }); await waitFor(() => { - expect(mockGui.getSelectTokens).toHaveBeenCalled(); + expect(mockBuilder.getSelectTokens).toHaveBeenCalled(); }); vi.clearAllMocks(); @@ -498,7 +498,7 @@ describe('DeploymentSteps', () => { accountStore.set(null); // await new Promise((resolve) => setTimeout(resolve, 100)); - expect(mockGui.getTokenInfo).not.toHaveBeenCalled(); - expect(mockGui.getAccountBalance).not.toHaveBeenCalled(); + expect(mockBuilder.getTokenInfo).not.toHaveBeenCalled(); + expect(mockBuilder.getAccountBalance).not.toHaveBeenCalled(); }); }); diff --git a/packages/ui-components/src/__tests__/DepositInput.test.ts b/packages/ui-components/src/__tests__/DepositInput.test.ts index 80b6defc89..5c7fd2f56a 100644 --- a/packages/ui-components/src/__tests__/DepositInput.test.ts +++ b/packages/ui-components/src/__tests__/DepositInput.test.ts @@ -1,33 +1,33 @@ import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { render, fireEvent, waitFor } from '@testing-library/svelte'; import DepositInput from '../lib/components/deployment/DepositInput.svelte'; -import type { GuiDepositCfg } from '@rainlanguage/orderbook'; +import type { OrderBuilderDepositCfg } from '@rainlanguage/orderbook'; import type { ComponentProps } from 'svelte'; -import { DotrainOrderGui } from '@rainlanguage/orderbook'; -import { useGui } from '$lib/hooks/useGui'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; type DepositInputProps = ComponentProps; vi.mock('@rainlanguage/orderbook', () => ({ - DotrainOrderGui: vi.fn() + RaindexOrderBuilder: vi.fn() })); -vi.mock('$lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('$lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); describe('DepositInput', () => { let mockStateUpdateCallback: Mock; - let guiInstance: DotrainOrderGui; + let builderInstance: RaindexOrderBuilder; - const mockDeposit: GuiDepositCfg = { + const mockDeposit: OrderBuilderDepositCfg = { token: { address: '0x123', key: 'TEST', symbol: 'TEST' }, presets: ['100', '200', '300'] - } as unknown as GuiDepositCfg; + } as unknown as OrderBuilderDepositCfg; beforeEach(() => { vi.clearAllMocks(); - guiInstance = { + builderInstance = { getDeposits: vi.fn().mockReturnValue({ value: [{ token: 'output', amount: '10', address: '0x1234' }] }), @@ -35,14 +35,14 @@ describe('DepositInput', () => { mockStateUpdateCallback(); }), getTokenInfo: vi.fn() - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; mockStateUpdateCallback = vi.fn(); - (useGui as Mock).mockReturnValue(guiInstance); + (useRaindexOrderBuilder as Mock).mockReturnValue(builderInstance); }); it('renders token name and presets', async () => { - (guiInstance.getTokenInfo as Mock).mockResolvedValueOnce({ + (builderInstance.getTokenInfo as Mock).mockResolvedValueOnce({ value: { name: 'Test Token', symbol: 'TEST' @@ -70,7 +70,7 @@ describe('DepositInput', () => { }); await fireEvent.click(getByText('100')); - expect(guiInstance.setDeposit).toHaveBeenCalledWith('TEST', '100'); + expect(builderInstance.setDeposit).toHaveBeenCalledWith('TEST', '100'); }); it('handles custom input changes and triggers state update', async () => { @@ -84,7 +84,7 @@ describe('DepositInput', () => { const input = getByPlaceholderText('Enter deposit amount'); await fireEvent.input(input, { target: { value: '150' } }); - expect(guiInstance.setDeposit).toHaveBeenCalledWith('TEST', '150'); + expect(builderInstance.setDeposit).toHaveBeenCalledWith('TEST', '150'); expect(mockStateUpdateCallback).toHaveBeenCalled(); }); }); diff --git a/packages/ui-components/src/__tests__/DotrainRegistryProvider.test.ts b/packages/ui-components/src/__tests__/DotrainRegistryProvider.test.ts index 5e12100806..64e098c971 100644 --- a/packages/ui-components/src/__tests__/DotrainRegistryProvider.test.ts +++ b/packages/ui-components/src/__tests__/DotrainRegistryProvider.test.ts @@ -29,7 +29,7 @@ describe('DotrainRegistryProvider', () => { getAllOrderDetails: vi.fn(() => wasmErrorResult), getOrderKeys: vi.fn(() => ({ value: [], error: undefined })), getDeploymentDetails: vi.fn(() => wasmErrorResult), - getGui: vi.fn(async () => wasmErrorResult), + getOrderBuilder: vi.fn(async () => wasmErrorResult), getOrderbookYaml: vi.fn(() => wasmErrorResult), getRaindexClient: vi.fn(async () => wasmErrorResult), registryUrl, diff --git a/packages/ui-components/src/__tests__/FieldDefinitionInput.test.ts b/packages/ui-components/src/__tests__/FieldDefinitionInput.test.ts index aafcec1895..804008e209 100644 --- a/packages/ui-components/src/__tests__/FieldDefinitionInput.test.ts +++ b/packages/ui-components/src/__tests__/FieldDefinitionInput.test.ts @@ -1,23 +1,23 @@ import { render, fireEvent } from '@testing-library/svelte'; import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import FieldDefinitionInput from '../lib/components/deployment/FieldDefinitionInput.svelte'; -import { DotrainOrderGui } from '@rainlanguage/orderbook'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; import userEvent from '@testing-library/user-event'; -import { useGui } from '$lib/hooks/useGui'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; import type { ComponentProps } from 'svelte'; type FieldDefinitionInputProps = ComponentProps; vi.mock('@rainlanguage/orderbook', () => ({ - DotrainOrderGui: vi.fn() + RaindexOrderBuilder: vi.fn() })); -vi.mock('$lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('$lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); describe('FieldDefinitionInput', () => { - let guiInstance: DotrainOrderGui; + let builderInstance: RaindexOrderBuilder; let mockStateUpdateCallback: Mock; const mockFieldDefinition = { @@ -33,14 +33,14 @@ describe('FieldDefinitionInput', () => { beforeEach(() => { mockStateUpdateCallback = vi.fn(); - guiInstance = { + builderInstance = { getFieldValue: vi.fn().mockReturnValue({}), setFieldValue: vi.fn().mockImplementation(() => { mockStateUpdateCallback(); }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(guiInstance); + (useRaindexOrderBuilder as Mock).mockReturnValue(builderInstance); }); it('renders field name and description', () => { @@ -74,7 +74,7 @@ describe('FieldDefinitionInput', () => { await fireEvent.click(getByText('Preset 1')); - expect(guiInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'value1'); + expect(builderInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'value1'); expect(mockStateUpdateCallback).toHaveBeenCalled(); }); @@ -88,7 +88,7 @@ describe('FieldDefinitionInput', () => { const input = getByPlaceholderText('Enter custom value'); await fireEvent.input(input, { target: { value: 'custom value' } }); - expect(guiInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'custom value'); + expect(builderInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'custom value'); expect(mockStateUpdateCallback).toHaveBeenCalled(); }); @@ -123,10 +123,10 @@ describe('FieldDefinitionInput', () => { await userEvent.type(input, '@'); - expect(guiInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'default value@'); + expect(builderInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'default value@'); }); it('renders selected value instead of default value', async () => { - (guiInstance.getFieldValue as Mock).mockReturnValue({ + (builderInstance.getFieldValue as Mock).mockReturnValue({ value: { binding: 'test-binding', value: 'preset1', @@ -149,6 +149,6 @@ describe('FieldDefinitionInput', () => { await userEvent.type(input, '@'); - expect(guiInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'preset1@'); + expect(builderInstance.setFieldValue).toHaveBeenCalledWith('test-binding', 'preset1@'); }); }); diff --git a/packages/ui-components/src/__tests__/OrderDetail.test.ts b/packages/ui-components/src/__tests__/OrderDetail.test.ts index 81c24a55f6..159e3a4ccf 100644 --- a/packages/ui-components/src/__tests__/OrderDetail.test.ts +++ b/packages/ui-components/src/__tests__/OrderDetail.test.ts @@ -445,38 +445,38 @@ describe('OrderDetail', () => { }); }); - it('renders the GUI state tab with formatted JSON when present', async () => { + it('renders the builder state tab with formatted JSON when present', async () => { const user = userEvent.setup(); - const guiState = JSON.stringify({ foo: 'bar' }); - resolveOrder({ dotrainGuiState: guiState }); + const builderState = JSON.stringify({ foo: 'bar' }); + resolveOrder({ orderBuilderState: builderState }); render(OrderDetail, { props: defaultProps, context: new Map([['$$_queryClient', queryClient]]) }); - const guiTab = await screen.findByText('Gui State'); - await user.click(guiTab); + const builderTab = await screen.findByText('Builder State'); + await user.click(builderTab); await waitFor(() => { - expect(screen.getByTestId('gui-state-json')).toHaveTextContent('"foo": "bar"'); + expect(screen.getByTestId('builder-state-json')).toHaveTextContent('"foo": "bar"'); }); }); - it('handles invalid GUI state JSON gracefully', async () => { + it('handles invalid builder state JSON gracefully', async () => { const user = userEvent.setup(); - resolveOrder({ dotrainGuiState: '{invalid' }); + resolveOrder({ orderBuilderState: '{invalid' }); render(OrderDetail, { props: defaultProps, context: new Map([['$$_queryClient', queryClient]]) }); - const guiTab = await screen.findByText('Gui State'); - await user.click(guiTab); + const builderTab = await screen.findByText('Builder State'); + await user.click(builderTab); await waitFor(() => { - expect(screen.getByTestId('gui-state-json')).toHaveTextContent('Invalid GUI state'); + expect(screen.getByTestId('builder-state-json')).toHaveTextContent('Invalid builder state'); }); }); diff --git a/packages/ui-components/src/__tests__/SelectToken.test.ts b/packages/ui-components/src/__tests__/SelectToken.test.ts index b705f66c11..6db9e16ab6 100644 --- a/packages/ui-components/src/__tests__/SelectToken.test.ts +++ b/packages/ui-components/src/__tests__/SelectToken.test.ts @@ -3,13 +3,13 @@ import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import SelectToken from '../lib/components/deployment/SelectToken.svelte'; import type { ComponentProps } from 'svelte'; -import { Float, type AccountBalance, type DotrainOrderGui } from '@rainlanguage/orderbook'; -import { useGui } from '$lib/hooks/useGui'; +import { Float, type AccountBalance, type RaindexOrderBuilder } from '@rainlanguage/orderbook'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; import type { TokenBalance } from '$lib/types/tokenBalance'; type SelectTokenComponentProps = ComponentProps; -const mockGui: DotrainOrderGui = { +const mockBuilder: RaindexOrderBuilder = { setSelectToken: vi.fn(), isSelectTokenSet: vi.fn(), unsetSelectToken: vi.fn(), @@ -39,7 +39,7 @@ const mockGui: DotrainOrderGui = { } ] }) -} as unknown as DotrainOrderGui; +} as unknown as RaindexOrderBuilder; vi.mock('@rainlanguage/orderbook', async (importOriginal) => { return { @@ -47,8 +47,8 @@ vi.mock('@rainlanguage/orderbook', async (importOriginal) => { }; }); -vi.mock('../lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('../lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); describe('SelectToken', () => { @@ -66,11 +66,11 @@ describe('SelectToken', () => { beforeEach(() => { mockStateUpdateCallback = vi.fn(); - mockGui.setSelectToken = vi.fn().mockImplementation(() => { + mockBuilder.setSelectToken = vi.fn().mockImplementation(() => { mockStateUpdateCallback(); return Promise.resolve(); }); - (useGui as Mock).mockReturnValue(mockGui); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilder); vi.clearAllMocks(); }); @@ -86,12 +86,12 @@ describe('SelectToken', () => { it('calls setSelectToken and updates token info when input changes', async () => { const user = userEvent.setup(); - const mockGuiWithNoToken = { - ...mockGui, + const mockBuilderWithNoToken = { + ...mockBuilder, getTokenInfo: vi.fn().mockResolvedValue({ value: null }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiWithNoToken); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderWithNoToken); const { getByTestId, getByRole } = render(SelectToken, { ...mockProps @@ -106,19 +106,19 @@ describe('SelectToken', () => { await user.paste('0x456'); await waitFor(() => { - expect(mockGuiWithNoToken.setSelectToken).toHaveBeenCalledWith('input', '0x456'); + expect(mockBuilderWithNoToken.setSelectToken).toHaveBeenCalledWith('input', '0x456'); }); expect(mockStateUpdateCallback).toHaveBeenCalledTimes(1); }); it('shows error message for invalid address, and removes the selectToken', async () => { const user = userEvent.setup(); - const mockGuiWithError = { - ...mockGui, + const mockBuilderWithError = { + ...mockBuilder, setSelectToken: vi.fn().mockRejectedValue(new Error('Invalid address')) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiWithError); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderWithError); const screen = render(SelectToken, { ...mockProps @@ -136,12 +136,12 @@ describe('SelectToken', () => { }); it('replaces the token and triggers state update twice if the token is already set', async () => { - const mockGuiWithTokenSet = { - ...mockGui, + const mockBuilderWithTokenSet = { + ...mockBuilder, isSelectTokenSet: vi.fn().mockResolvedValue(true) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiWithTokenSet); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderWithTokenSet); const user = userEvent.setup(); @@ -156,7 +156,7 @@ describe('SelectToken', () => { await userEvent.clear(input); await user.paste('invalid'); await waitFor(() => { - expect(mockGuiWithTokenSet.setSelectToken).toHaveBeenCalled(); + expect(mockBuilderWithTokenSet.setSelectToken).toHaveBeenCalled(); expect(mockStateUpdateCallback).toHaveBeenCalledTimes(1); }); }); @@ -180,7 +180,7 @@ describe('SelectToken', () => { describe('Dropdown Mode', () => { beforeEach(() => { - (useGui as Mock).mockReturnValue(mockGui); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilder); }); it('shows dropdown and custom mode buttons when tokens are available', () => { @@ -229,12 +229,12 @@ describe('SelectToken', () => { it('clears state when switching from dropdown to custom mode', async () => { const user = userEvent.setup(); - const mockGuiNoToken = { - ...mockGui, + const mockBuilderNoToken = { + ...mockBuilder, getTokenInfo: vi.fn().mockResolvedValue({ value: null }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiNoToken); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderNoToken); render(SelectToken, { ...mockProps @@ -252,7 +252,7 @@ describe('SelectToken', () => { const customInput = screen.getByPlaceholderText('Enter token address (0x...)'); expect(customInput).toHaveValue(''); - expect(mockGuiNoToken.unsetSelectToken).toHaveBeenCalledWith('input'); + expect(mockBuilderNoToken.unsetSelectToken).toHaveBeenCalledWith('input'); }); it('clears state when switching from custom to dropdown mode', async () => { @@ -268,17 +268,17 @@ describe('SelectToken', () => { const dropdownButton = screen.getByTestId('dropdown-mode-button'); await user.click(dropdownButton); - expect(mockGui.unsetSelectToken).toHaveBeenCalledWith('input'); + expect(mockBuilder.unsetSelectToken).toHaveBeenCalledWith('input'); }); it('handles token selection from dropdown', async () => { const user = userEvent.setup(); - const mockGuiNoToken = { - ...mockGui, + const mockBuilderNoToken = { + ...mockBuilder, getTokenInfo: vi.fn().mockResolvedValue({ value: null }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiNoToken); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderNoToken); render(SelectToken, { ...mockProps @@ -290,14 +290,14 @@ describe('SelectToken', () => { const secondToken = screen.getByText('Another Token'); await user.click(secondToken); - expect(mockGuiNoToken.setSelectToken).toHaveBeenCalledWith( + expect(mockBuilderNoToken.setSelectToken).toHaveBeenCalledWith( 'input', '0x0987654321098765432109876543210987654321' ); }); it('displays selected token info when token is selected', async () => { - mockGui.getTokenInfo = vi.fn().mockResolvedValue({ + mockBuilder.getTokenInfo = vi.fn().mockResolvedValue({ value: { name: 'Test Token 1', symbol: 'TEST1', @@ -318,7 +318,7 @@ describe('SelectToken', () => { describe('Balance Display', () => { it('displays balance when token is selected and balance is provided', async () => { - mockGui.getTokenInfo = vi.fn().mockResolvedValue({ + mockBuilder.getTokenInfo = vi.fn().mockResolvedValue({ value: { name: 'Test Token', symbol: 'TEST', @@ -353,7 +353,7 @@ describe('SelectToken', () => { }); it('shows loading spinner when balance is loading', async () => { - mockGui.getTokenInfo = vi.fn().mockResolvedValue({ + mockBuilder.getTokenInfo = vi.fn().mockResolvedValue({ value: { name: 'Test Token', symbol: 'TEST', @@ -388,7 +388,7 @@ describe('SelectToken', () => { }); it('shows error message when balance fetch fails', async () => { - mockGui.getTokenInfo = vi.fn().mockResolvedValue({ + mockBuilder.getTokenInfo = vi.fn().mockResolvedValue({ value: { name: 'Test Token', symbol: 'TEST', @@ -423,7 +423,7 @@ describe('SelectToken', () => { }); it('formats balance correctly with token decimals', async () => { - mockGui.getTokenInfo = vi.fn().mockResolvedValue({ + mockBuilder.getTokenInfo = vi.fn().mockResolvedValue({ value: { name: 'USDC', symbol: 'USDC', diff --git a/packages/ui-components/src/__tests__/TokenIOInput.test.ts b/packages/ui-components/src/__tests__/TokenIOInput.test.ts index dc5bf18167..4a3e3164d1 100644 --- a/packages/ui-components/src/__tests__/TokenIOInput.test.ts +++ b/packages/ui-components/src/__tests__/TokenIOInput.test.ts @@ -2,25 +2,25 @@ import { render, fireEvent, waitFor } from '@testing-library/svelte'; import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import TokenIOInput from '../lib/components/deployment/TokenIOInput.svelte'; import type { ComponentProps } from 'svelte'; -import { AccountBalance, DotrainOrderGui, Float } from '@rainlanguage/orderbook'; -import { useGui } from '$lib/hooks/useGui'; +import { AccountBalance, RaindexOrderBuilder, Float } from '@rainlanguage/orderbook'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; import type { TokenBalance } from '$lib/types/tokenBalance'; vi.mock('@rainlanguage/orderbook', async (importOriginal) => { return { ...(await importOriginal()), - DotrainOrderGui: vi.fn() + RaindexOrderBuilder: vi.fn() }; }); -vi.mock('$lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('$lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); type TokenIOInputComponentProps = ComponentProps; describe('TokenInput', () => { - let guiInstance: DotrainOrderGui; + let builderInstance: RaindexOrderBuilder; let mockStateUpdateCallback: Mock; let mockProps: TokenIOInputComponentProps; let outputMockProps: TokenIOInputComponentProps; @@ -43,7 +43,7 @@ describe('TokenInput', () => { vi.clearAllMocks(); // Create a mock instance with all the methods - guiInstance = { + builderInstance = { getTokenInfo: vi.fn().mockResolvedValue(mockTokenInfo), setVaultId: vi.fn().mockImplementation(() => { mockStateUpdateCallback(); @@ -61,11 +61,11 @@ describe('TokenInput', () => { ['output', new Map([['test', 'vault2']])] ]) }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; mockStateUpdateCallback = vi.fn(); - (useGui as Mock).mockReturnValue(guiInstance); + (useRaindexOrderBuilder as Mock).mockReturnValue(builderInstance); mockProps = { label: 'Input', @@ -100,14 +100,14 @@ describe('TokenInput', () => { it('calls setVaultId when input changes', async () => { const input = render(TokenIOInput, mockProps).getByPlaceholderText('Enter vault ID'); await fireEvent.input(input, { target: { value: 'vault1' } }); - expect(guiInstance.setVaultId).toHaveBeenCalledWith('input', 'test', 'vault1'); + expect(builderInstance.setVaultId).toHaveBeenCalledWith('input', 'test', 'vault1'); expect(mockStateUpdateCallback).toHaveBeenCalledTimes(1); }); it('calls setVaultId on output vault when input changes', async () => { const input = render(TokenIOInput, outputMockProps).getByPlaceholderText('Enter vault ID'); await fireEvent.input(input, { target: { value: 'vault2' } }); - expect(guiInstance.setVaultId).toHaveBeenCalledWith('output', 'test', 'vault2'); + expect(builderInstance.setVaultId).toHaveBeenCalledWith('output', 'test', 'vault2'); expect(mockStateUpdateCallback).toHaveBeenCalledTimes(1); }); @@ -217,6 +217,6 @@ describe('TokenInput', () => { const labelWithSymbol = await findByText('Input (MOCK)'); expect(labelWithSymbol).toBeInTheDocument(); - expect(guiInstance.getTokenInfo).toHaveBeenCalledWith('0x456'); + expect(builderInstance.getTokenInfo).toHaveBeenCalledWith('0x456'); }); }); diff --git a/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts b/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts index ee7b51ef42..146056e19d 100644 --- a/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts +++ b/packages/ui-components/src/__tests__/TokenSelectionModal.test.ts @@ -3,8 +3,8 @@ import userEvent from '@testing-library/user-event'; import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import TokenSelectionModal from '../lib/components/deployment/TokenSelectionModal.svelte'; import type { ComponentProps } from 'svelte'; -import type { ExtendedTokenInfo, DotrainOrderGui } from '@rainlanguage/orderbook'; -import { useGui } from '$lib/hooks/useGui'; +import type { ExtendedTokenInfo, RaindexOrderBuilder } from '@rainlanguage/orderbook'; +import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; type TokenSelectionModalProps = ComponentProps; @@ -29,14 +29,14 @@ const mockTokens: ExtendedTokenInfo[] = [ } ]; -const mockGui: DotrainOrderGui = { +const mockBuilder: RaindexOrderBuilder = { getAllTokens: vi.fn().mockResolvedValue({ value: mockTokens }) -} as unknown as DotrainOrderGui; +} as unknown as RaindexOrderBuilder; -vi.mock('../lib/hooks/useGui', () => ({ - useGui: vi.fn() +vi.mock('../lib/hooks/useRaindexOrderBuilder', () => ({ + useRaindexOrderBuilder: vi.fn() })); describe('TokenSelectionModal', () => { @@ -49,7 +49,7 @@ describe('TokenSelectionModal', () => { beforeEach(() => { mockOnSelect = vi.fn(); - (useGui as Mock).mockReturnValue(mockGui); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilder); vi.clearAllMocks(); }); @@ -109,7 +109,7 @@ describe('TokenSelectionModal', () => { await userEvent.click(button); await waitFor(() => { - expect(mockGui.getAllTokens).toHaveBeenCalledWith(undefined); + expect(mockBuilder.getAllTokens).toHaveBeenCalledWith(undefined); }); }); @@ -163,20 +163,20 @@ describe('TokenSelectionModal', () => { await user.type(searchInput, 'TEST'); await waitFor(() => { - expect(mockGui.getAllTokens).toHaveBeenCalledWith('TEST'); + expect(mockBuilder.getAllTokens).toHaveBeenCalledWith('TEST'); }); }); it('shows loading state while searching', async () => { - const mockGuiWithDelay = { + const mockBuilderWithDelay = { getAllTokens: vi .fn() .mockImplementation( () => new Promise((resolve) => setTimeout(() => resolve({ value: mockTokens }), 100)) ) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiWithDelay); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderWithDelay); const user = userEvent.setup(); render(TokenSelectionModal, { @@ -194,11 +194,11 @@ describe('TokenSelectionModal', () => { }); it('shows no results message when search returns empty', async () => { - const mockGuiNoResults = { + const mockBuilderNoResults = { getAllTokens: vi.fn().mockResolvedValue({ value: [] }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiNoResults); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderNoResults); const user = userEvent.setup(); render(TokenSelectionModal, { @@ -215,14 +215,14 @@ describe('TokenSelectionModal', () => { }); it('clears search when clear button is clicked', async () => { - const mockGuiNoResults = { + const mockBuilderNoResults = { getAllTokens: vi .fn() .mockResolvedValueOnce({ value: [] }) .mockResolvedValueOnce({ value: mockTokens }) - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; - (useGui as Mock).mockReturnValue(mockGuiNoResults); + (useRaindexOrderBuilder as Mock).mockReturnValue(mockBuilderNoResults); const user = userEvent.setup(); render(TokenSelectionModal, { @@ -241,7 +241,7 @@ describe('TokenSelectionModal', () => { await user.click(clearButton); await waitFor(() => { - expect(mockGuiNoResults.getAllTokens).toHaveBeenCalledWith(undefined); + expect(mockBuilderNoResults.getAllTokens).toHaveBeenCalledWith(undefined); }); }); diff --git a/packages/ui-components/src/__tests__/handleShareChoices.test.ts b/packages/ui-components/src/__tests__/handleShareChoices.test.ts index f354f08756..ae96c569f8 100644 --- a/packages/ui-components/src/__tests__/handleShareChoices.test.ts +++ b/packages/ui-components/src/__tests__/handleShareChoices.test.ts @@ -1,19 +1,19 @@ import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest'; import { handleShareChoices } from '../lib/services/handleShareChoices'; -import { DotrainOrderGui } from '@rainlanguage/orderbook'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; vi.mock('@rainlanguage/orderbook', () => ({ - DotrainOrderGui: vi.fn() + RaindexOrderBuilder: vi.fn() })); describe('handleShareChoices', () => { - let guiInstance: DotrainOrderGui; + let builderInstance: RaindexOrderBuilder; const mockRegistryUrl = 'https://example.com/registry'; beforeEach(() => { - guiInstance = { + builderInstance = { serializeState: vi.fn() - } as unknown as DotrainOrderGui; + } as unknown as RaindexOrderBuilder; Object.assign(navigator, { clipboard: { @@ -31,9 +31,9 @@ describe('handleShareChoices', () => { }); it('should share the choices with state and registry', async () => { - (guiInstance.serializeState as Mock).mockReturnValue({ value: 'mockState123' }); + (builderInstance.serializeState as Mock).mockReturnValue({ value: 'mockState123' }); - await handleShareChoices(guiInstance, mockRegistryUrl); + await handleShareChoices(builderInstance, mockRegistryUrl); expect(navigator.clipboard.writeText).toHaveBeenCalledWith( 'http://example.com/?state=mockState123®istry=https%3A%2F%2Fexample.com%2Fregistry' @@ -41,9 +41,9 @@ describe('handleShareChoices', () => { }); it('should handle null state', async () => { - (guiInstance.serializeState as Mock).mockReturnValue({ value: null }); + (builderInstance.serializeState as Mock).mockReturnValue({ value: null }); - await handleShareChoices(guiInstance, mockRegistryUrl); + await handleShareChoices(builderInstance, mockRegistryUrl); expect(navigator.clipboard.writeText).toHaveBeenCalledWith( 'http://example.com/?state=®istry=https%3A%2F%2Fexample.com%2Fregistry' diff --git a/packages/ui-components/src/__tests__/registry.test.ts b/packages/ui-components/src/__tests__/registry.test.ts index b293e5dcfd..78c356dee3 100644 --- a/packages/ui-components/src/__tests__/registry.test.ts +++ b/packages/ui-components/src/__tests__/registry.test.ts @@ -4,12 +4,12 @@ import { fetchRegistryDotrains, validateOrders } from '../lib/services/registry'; -import { DotrainOrderGui } from '@rainlanguage/orderbook'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; import type { Mock } from 'vitest'; -// Mock the DotrainOrderGui dependency +// Mock the RaindexOrderBuilder dependency vi.mock('@rainlanguage/orderbook', () => ({ - DotrainOrderGui: { + RaindexOrderBuilder: { getOrderDetails: vi.fn() } })); @@ -128,8 +128,8 @@ describe('validateOrders', () => { { name: 'another-valid.rain', dotrain: 'another valid content' } ]; - // Set up mock responses for the DotrainOrderGui - (DotrainOrderGui.getOrderDetails as Mock) + // Set up mock responses for the RaindexOrderBuilder + (RaindexOrderBuilder.getOrderDetails as Mock) .mockResolvedValueOnce({ value: { name: 'Valid Order', description: 'A valid order' }, error: null @@ -146,11 +146,11 @@ describe('validateOrders', () => { // Call the function with our test data const result = await validateOrders(registryDotrains); - // Verify DotrainOrderGui was called correctly - expect(DotrainOrderGui.getOrderDetails).toHaveBeenCalledTimes(3); - expect(DotrainOrderGui.getOrderDetails).toHaveBeenCalledWith('valid dotrain content'); - expect(DotrainOrderGui.getOrderDetails).toHaveBeenCalledWith('invalid dotrain content'); - expect(DotrainOrderGui.getOrderDetails).toHaveBeenCalledWith('another valid content'); + // Verify RaindexOrderBuilder was called correctly + expect(RaindexOrderBuilder.getOrderDetails).toHaveBeenCalledTimes(3); + expect(RaindexOrderBuilder.getOrderDetails).toHaveBeenCalledWith('valid dotrain content'); + expect(RaindexOrderBuilder.getOrderDetails).toHaveBeenCalledWith('invalid dotrain content'); + expect(RaindexOrderBuilder.getOrderDetails).toHaveBeenCalledWith('another valid content'); // Verify the valid orders are processed correctly expect(result.validOrders).toHaveLength(2); @@ -171,8 +171,8 @@ describe('validateOrders', () => { // Input data const registryDotrains = [{ name: 'error.rain', dotrain: 'will throw error' }]; - // Mock the DotrainOrderGui to throw an exception - (DotrainOrderGui.getOrderDetails as Mock).mockRejectedValueOnce( + // Mock the RaindexOrderBuilder to throw an exception + (RaindexOrderBuilder.getOrderDetails as Mock).mockRejectedValueOnce( new Error('Unexpected parsing error') ); @@ -190,8 +190,8 @@ describe('validateOrders', () => { // Input data const registryDotrains = [{ name: 'string-error.rain', dotrain: 'will throw string' }]; - // Mock the DotrainOrderGui to throw a string instead of an Error - (DotrainOrderGui.getOrderDetails as Mock).mockRejectedValueOnce('String error message'); + // Mock the RaindexOrderBuilder to throw a string instead of an Error + (RaindexOrderBuilder.getOrderDetails as Mock).mockRejectedValueOnce('String error message'); // Call the function const result = await validateOrders(registryDotrains); @@ -208,7 +208,7 @@ describe('validateOrders', () => { expect(result.validOrders).toEqual([]); expect(result.invalidOrders).toEqual([]); - expect(DotrainOrderGui.getOrderDetails).not.toHaveBeenCalled(); + expect(RaindexOrderBuilder.getOrderDetails).not.toHaveBeenCalled(); }); it('should handle mixed validation results correctly', async () => { @@ -221,7 +221,7 @@ describe('validateOrders', () => { ]; // Set up mock responses - (DotrainOrderGui.getOrderDetails as Mock) + (RaindexOrderBuilder.getOrderDetails as Mock) .mockResolvedValueOnce({ value: { orderName: 'Order 1', description: 'Description 1' }, error: null diff --git a/packages/ui-components/src/lib/components/deployment/ComposedRainlangModal.svelte b/packages/ui-components/src/lib/components/deployment/ComposedRainlangModal.svelte index 1ff589c1ed..91b91e64ff 100644 --- a/packages/ui-components/src/lib/components/deployment/ComposedRainlangModal.svelte +++ b/packages/ui-components/src/lib/components/deployment/ComposedRainlangModal.svelte @@ -3,15 +3,15 @@ import { RainlangLR } from 'codemirror-rainlang'; import { lightCodeMirrorTheme } from '../../utils/codeMirrorThemes'; import { Button, Modal } from 'flowbite-svelte'; - import { useGui } from '$lib/hooks/useGui'; + import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; - const gui = useGui(); + const builder = useRaindexOrderBuilder(); let rainlangText: string | null = null; let open = false; async function generateRainlang() { - let result = await gui.getComposedRainlang(); + let result = await builder.getComposedRainlang(); if (result.error) { throw new Error(result.error.msg); } diff --git a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte index f44002b819..6892a96f25 100644 --- a/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte +++ b/packages/ui-components/src/lib/components/deployment/DeploymentSteps.svelte @@ -3,13 +3,13 @@ import TokenIOInput from './TokenIOInput.svelte'; import ComposedRainlangModal from './ComposedRainlangModal.svelte'; import { - type GuiSelectTokensCfg, + type OrderBuilderSelectTokensCfg, type ExtendedTokenInfo, - type GuiDepositCfg, - type GuiFieldDefinitionCfg, + type OrderBuilderDepositCfg, + type OrderBuilderFieldDefinitionCfg, type NameAndDescriptionCfg, type OrderIOCfg, - DotrainOrderGui, + RaindexOrderBuilder, RaindexClient, AccountBalance, Float @@ -24,7 +24,7 @@ import DepositInput from './DepositInput.svelte'; import SelectToken from './SelectToken.svelte'; import DeploymentSectionHeader from './DeploymentSectionHeader.svelte'; - import { useGui } from '$lib/hooks/useGui'; + import { useRaindexOrderBuilder } from '$lib/hooks/useRaindexOrderBuilder'; import { fade } from 'svelte/transition'; import ShareChoicesButton from './ShareChoicesButton.svelte'; import { useRegistry } from '$lib/providers/registry/useRegistry'; @@ -43,31 +43,31 @@ /** Strategy details containing name and description configuration */ export let orderDetail: NameAndDescriptionCfg; /** Handlers for deployment modals */ - export let onDeploy: (raindexClient: RaindexClient, gui: DotrainOrderGui) => void; + export let onDeploy: (raindexClient: RaindexClient, builder: RaindexOrderBuilder) => void; export let wagmiConnected: Writable; export let appKitModal: Writable; export let account: Account; - let allDepositFields: GuiDepositCfg[] = []; + let allDepositFields: OrderBuilderDepositCfg[] = []; let allTokenOutputs: OrderIOCfg[] = []; let allTokenInputs: OrderIOCfg[] = []; - let allFieldDefinitionsWithoutDefaults: GuiFieldDefinitionCfg[] = []; - let allFieldDefinitionsWithDefaults: GuiFieldDefinitionCfg[] = []; + let allFieldDefinitionsWithoutDefaults: OrderBuilderFieldDefinitionCfg[] = []; + let allFieldDefinitionsWithDefaults: OrderBuilderFieldDefinitionCfg[] = []; let allTokensSelected: boolean = false; let showAdvancedOptions: boolean = false; let allTokenInfos: ExtendedTokenInfo[] = []; - let selectTokens: GuiSelectTokensCfg[] | undefined = undefined; + let selectTokens: OrderBuilderSelectTokensCfg[] | undefined = undefined; let checkingDeployment: boolean = false; let tokenBalances: Map = new Map(); - const gui = useGui(); + const builder = useRaindexOrderBuilder(); const registry = useRegistry(); const raindexClient = useRaindexClient(); let deploymentStepsError = DeploymentStepsError.error; onMount(async () => { - const selectTokensResult = gui.getSelectTokens(); + const selectTokensResult = builder.getSelectTokens(); if (selectTokensResult.error) { throw new Error(selectTokensResult.error.msg); } @@ -96,9 +96,9 @@ unsubscribeAccount(); }); - function getAllGuiConfig() { + function getAllBuilderConfig() { try { - let result = gui.getAllGuiConfig(); + let result = builder.getAllBuilderConfig(); if (result.error) { throw new Error(result.error.msg); } @@ -108,21 +108,21 @@ allTokenOutputs = result.value.orderOutputs; allTokenInputs = result.value.orderInputs; } catch (e) { - DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_GUI_CONFIG); + DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_BUILDER_CONFIG); } } function updateFields() { try { DeploymentStepsError.clear(); - getAllGuiConfig(); + getAllBuilderConfig(); } catch (e) { - DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_GUI); + DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_BUILDER); } } async function _handleShareChoices() { - await handleShareChoices(gui, registry.getCurrentRegistry()); + await handleShareChoices(builder, registry.getCurrentRegistry()); } async function fetchTokenBalance(tokenInfo: ExtendedTokenInfo) { @@ -135,7 +135,7 @@ error: '' }); - const { value: accountBalance, error } = await gui.getAccountBalance( + const { value: accountBalance, error } = await builder.getAccountBalance( tokenInfo.address, $account ); @@ -157,7 +157,7 @@ } async function getTokenInfoAndFetchBalance(key: string) { - const tokenInfoResult = await gui.getTokenInfo(key); + const tokenInfoResult = await builder.getTokenInfo(key); if (tokenInfoResult.error) { throw new Error(tokenInfoResult.error.msg); } @@ -174,39 +174,39 @@ await getTokenInfoAndFetchBalance(key); if (allTokensSelected) { - let result = await gui.getAllTokenInfos(); + let result = await builder.getAllTokenInfos(); if (result.error) { throw new Error(result.error.msg); } let newAllTokenInfos = result.value; if (allTokenInfos !== newAllTokenInfos) { allTokenInfos = newAllTokenInfos; - getAllGuiConfig(); + getAllBuilderConfig(); } } } const areAllTokensSelected = async () => { try { - const areAllTokensSelectedResult = gui.areAllTokensSelected(); + const areAllTokensSelectedResult = builder.areAllTokensSelected(); if (areAllTokensSelectedResult.error) { throw new Error(areAllTokensSelectedResult.error.msg); } allTokensSelected = areAllTokensSelectedResult.value; if (!allTokensSelected) return; - const getAllTokenInfosResult = await gui.getAllTokenInfos(); + const getAllTokenInfosResult = await builder.getAllTokenInfos(); if (getAllTokenInfosResult.error) { throw new Error(getAllTokenInfosResult.error.msg); } allTokenInfos = getAllTokenInfosResult.value; // if we have deposits or vault ids set, show advanced options - const hasDepositsResult = gui.hasAnyDeposit(); + const hasDepositsResult = builder.hasAnyDeposit(); if (hasDepositsResult.error) { throw new Error(hasDepositsResult.error.msg); } - const hasVaultIdsResult = gui.hasAnyVaultId(); + const hasVaultIdsResult = builder.hasAnyVaultId(); if (hasVaultIdsResult.error) { throw new Error(hasVaultIdsResult.error.msg); } @@ -233,7 +233,7 @@ } DeploymentStepsError.clear(); - return onDeploy(raindexClient, gui); + return onDeploy(raindexClient, builder); } catch (e) { DeploymentStepsError.catch(e, DeploymentStepsErrorCode.ADD_ORDER_FAILED); } finally { @@ -243,7 +243,7 @@
- {#if gui} + {#if builder}
{#if deployment}
diff --git a/packages/ui-components/src/lib/components/deployment/DepositInput.svelte b/packages/ui-components/src/lib/components/deployment/DepositInput.svelte index 73d085736c..91a03fbf69 100644 --- a/packages/ui-components/src/lib/components/deployment/DepositInput.svelte +++ b/packages/ui-components/src/lib/components/deployment/DepositInput.svelte @@ -1,15 +1,19 @@ @@ -339,12 +339,12 @@ >
- {#if data.dotrainGuiState} - + {#if data.orderBuilderState} +
-
-								{formatGuiState(data.dotrainGuiState)}
+							
+								{formatBuilderState(data.orderBuilderState)}
 							
diff --git a/packages/ui-components/src/lib/errors/DeploymentStepsError.ts b/packages/ui-components/src/lib/errors/DeploymentStepsError.ts index f445fe962e..f808c33730 100644 --- a/packages/ui-components/src/lib/errors/DeploymentStepsError.ts +++ b/packages/ui-components/src/lib/errors/DeploymentStepsError.ts @@ -1,8 +1,8 @@ import { writable } from 'svelte/store'; export enum DeploymentStepsErrorCode { - NO_GUI_PROVIDER = 'No GUI provider found.', - NO_GUI = 'Error loading GUI.', + NO_BUILDER_PROVIDER = 'No builder provider found.', + NO_BUILDER = 'Error loading builder.', NO_LOCAL_DB_PROVIDER = 'No Local DB provider found.', NO_STRATEGY = 'No valid order exists at this URL', NO_SELECT_TOKENS = 'Error loading tokens', @@ -11,14 +11,14 @@ export enum DeploymentStepsErrorCode { NO_DEPOSITS = 'Error loading deposits', NO_TOKEN_INPUTS = 'Error loading token inputs', NO_TOKEN_OUTPUTS = 'Error loading token outputs', - NO_GUI_DETAILS = 'Error getting GUI details', + NO_BUILDER_DETAILS = 'Error getting builder details', NO_CHAIN = 'Unsupported chain ID', NO_NETWORK_KEY = 'No network key found', NO_AVAILABLE_TOKENS = 'Error loading available tokens', SERIALIZE_ERROR = 'Error serializing state', ADD_ORDER_FAILED = 'Failed to add order', NO_WALLET = 'No account address found', - NO_GUI_CONFIG = 'Error getting GUI configuration', + NO_BUILDER_CONFIG = 'Error getting builder configuration', NO_RAINDEX_CLIENT_PROVIDER = 'No Raindex client provider found' } diff --git a/packages/ui-components/src/lib/hooks/useGui.test.ts b/packages/ui-components/src/lib/hooks/useGui.test.ts deleted file mode 100644 index 8a68e9d512..0000000000 --- a/packages/ui-components/src/lib/hooks/useGui.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { getContext } from 'svelte'; -import { useGui, GUI_CONTEXT_KEY } from './useGui'; -import { DeploymentStepsError, DeploymentStepsErrorCode } from '../errors/DeploymentStepsError'; - -vi.mock('svelte', () => ({ - getContext: vi.fn() -})); - -vi.spyOn(DeploymentStepsError, 'catch'); - -describe('useGui hook', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - it('should return GUI context when available', () => { - const mockGui = { - someMethod: vi.fn(), - someProperty: 'value' - }; - - vi.mocked(getContext).mockReturnValue(mockGui); - - const result = useGui(); - - expect(getContext).toHaveBeenCalledWith(GUI_CONTEXT_KEY); - expect(result).toBe(mockGui); - }); - - it('should call DeploymentStepsError.catch when GUI context is not available', () => { - vi.mocked(getContext).mockReturnValue(null); - - useGui(); - - expect(DeploymentStepsError.catch).toHaveBeenCalledWith( - null, - DeploymentStepsErrorCode.NO_GUI_PROVIDER - ); - }); -}); diff --git a/packages/ui-components/src/lib/hooks/useGui.ts b/packages/ui-components/src/lib/hooks/useGui.ts deleted file mode 100644 index 5d8636dedb..0000000000 --- a/packages/ui-components/src/lib/hooks/useGui.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { getContext } from 'svelte'; -import { DotrainOrderGui } from '@rainlanguage/orderbook'; -import { DeploymentStepsError, DeploymentStepsErrorCode } from '../errors/DeploymentStepsError'; -export const GUI_CONTEXT_KEY = 'gui-context'; - -export function useGui(): DotrainOrderGui { - const gui = getContext(GUI_CONTEXT_KEY); - if (!gui) { - DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_GUI_PROVIDER); - } - return gui; -} diff --git a/packages/ui-components/src/lib/hooks/useRaindexOrderBuilder.test.ts b/packages/ui-components/src/lib/hooks/useRaindexOrderBuilder.test.ts new file mode 100644 index 0000000000..1678adc0bc --- /dev/null +++ b/packages/ui-components/src/lib/hooks/useRaindexOrderBuilder.test.ts @@ -0,0 +1,48 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { getContext } from 'svelte'; +import { + useRaindexOrderBuilder, + RAINDEX_ORDER_BUILDER_CONTEXT_KEY +} from './useRaindexOrderBuilder'; +import { DeploymentStepsError, DeploymentStepsErrorCode } from '../errors/DeploymentStepsError'; + +vi.mock('svelte', () => ({ + getContext: vi.fn() +})); + +vi.spyOn(DeploymentStepsError, 'catch'); + +describe('useRaindexOrderBuilder hook', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should return builder context when available', () => { + const mockBuilder = { + someMethod: vi.fn(), + someProperty: 'value' + }; + + vi.mocked(getContext).mockReturnValue(mockBuilder); + + const result = useRaindexOrderBuilder(); + + expect(getContext).toHaveBeenCalledWith(RAINDEX_ORDER_BUILDER_CONTEXT_KEY); + expect(result).toBe(mockBuilder); + }); + + it('should call DeploymentStepsError.catch when builder context is not available', () => { + vi.mocked(getContext).mockReturnValue(null); + + useRaindexOrderBuilder(); + + expect(DeploymentStepsError.catch).toHaveBeenCalledWith( + null, + DeploymentStepsErrorCode.NO_BUILDER_PROVIDER + ); + }); +}); diff --git a/packages/ui-components/src/lib/hooks/useRaindexOrderBuilder.ts b/packages/ui-components/src/lib/hooks/useRaindexOrderBuilder.ts new file mode 100644 index 0000000000..06c4a756cd --- /dev/null +++ b/packages/ui-components/src/lib/hooks/useRaindexOrderBuilder.ts @@ -0,0 +1,12 @@ +import { getContext } from 'svelte'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; +import { DeploymentStepsError, DeploymentStepsErrorCode } from '../errors/DeploymentStepsError'; +export const RAINDEX_ORDER_BUILDER_CONTEXT_KEY = 'raindex-order-builder-context'; + +export function useRaindexOrderBuilder(): RaindexOrderBuilder { + const builder = getContext(RAINDEX_ORDER_BUILDER_CONTEXT_KEY); + if (!builder) { + DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_BUILDER_PROVIDER); + } + return builder; +} diff --git a/packages/ui-components/src/lib/index.ts b/packages/ui-components/src/lib/index.ts index e421b6ce9b..44cb0f9ade 100644 --- a/packages/ui-components/src/lib/index.ts +++ b/packages/ui-components/src/lib/index.ts @@ -136,7 +136,7 @@ export { default as logoLight } from './assets/logo-light.svg'; export { default as logoDark } from './assets/logo-dark.svg'; // Providers -export { default as GuiProvider } from './providers/GuiProvider.svelte'; +export { default as RaindexOrderBuilderProvider } from './providers/RaindexOrderBuilderProvider.svelte'; export { default as RaindexClientProvider } from './providers/RaindexClientProvider.svelte'; export { default as WalletProvider } from './providers/wallet/WalletProvider.svelte'; export { default as RegistryProvider } from './providers/registry/RegistryProvider.svelte'; @@ -146,7 +146,7 @@ export { default as LocalDbProvider } from './providers/LocalDbProvider.svelte'; export { default as DotrainRegistryProvider } from './providers/dotrainRegistry/DotrainRegistryProvider.svelte'; // Hooks -export { useGui } from './hooks/useGui'; +export { useRaindexOrderBuilder } from './hooks/useRaindexOrderBuilder'; export { useRaindexClient, RAINDEX_CLIENT_CONTEXT_KEY } from './hooks/useRaindexClient'; export { useLocalDb } from './hooks/useLocalDb'; export { useAccount } from './providers/wallet/useAccount'; diff --git a/packages/ui-components/src/lib/providers/GuiProvider.svelte b/packages/ui-components/src/lib/providers/GuiProvider.svelte deleted file mode 100644 index 3ea838c727..0000000000 --- a/packages/ui-components/src/lib/providers/GuiProvider.svelte +++ /dev/null @@ -1,11 +0,0 @@ - - - diff --git a/packages/ui-components/src/lib/providers/RaindexOrderBuilderProvider.svelte b/packages/ui-components/src/lib/providers/RaindexOrderBuilderProvider.svelte new file mode 100644 index 0000000000..b9e6e32469 --- /dev/null +++ b/packages/ui-components/src/lib/providers/RaindexOrderBuilderProvider.svelte @@ -0,0 +1,11 @@ + + + diff --git a/packages/ui-components/src/lib/services/handleShareChoices.ts b/packages/ui-components/src/lib/services/handleShareChoices.ts index adc3cec592..67490015f9 100644 --- a/packages/ui-components/src/lib/services/handleShareChoices.ts +++ b/packages/ui-components/src/lib/services/handleShareChoices.ts @@ -1,13 +1,13 @@ -import type { DotrainOrderGui } from '@rainlanguage/orderbook'; +import type { RaindexOrderBuilder } from '@rainlanguage/orderbook'; import { page } from '$app/stores'; import { get } from 'svelte/store'; -export async function handleShareChoices(gui: DotrainOrderGui, registryUrl: string) { +export async function handleShareChoices(builder: RaindexOrderBuilder, registryUrl: string) { // get the current url const url = get(page).url; // get the current state - const result = gui.serializeState(); + const result = builder.serializeState(); if (result.error) { throw new Error(result.error.msg); } diff --git a/packages/ui-components/src/lib/services/registry.ts b/packages/ui-components/src/lib/services/registry.ts index a147336e41..893169bc67 100644 --- a/packages/ui-components/src/lib/services/registry.ts +++ b/packages/ui-components/src/lib/services/registry.ts @@ -1,5 +1,5 @@ import type { InvalidOrderDetail, ValidOrderDetail } from '$lib/types/order'; -import { DotrainOrderGui } from '@rainlanguage/orderbook'; +import { RaindexOrderBuilder } from '@rainlanguage/orderbook'; export type RegistryFile = { name: string; @@ -80,7 +80,7 @@ export async function validateOrders( ): Promise { const ordersPromises = registryDotrains.map(async (registryDotrain) => { try { - const result = await DotrainOrderGui.getOrderDetails(registryDotrain.dotrain); + const result = await RaindexOrderBuilder.getOrderDetails(registryDotrain.dotrain); if (result.error) { throw new Error(result.error.msg); diff --git a/packages/ui-components/test-setup.ts b/packages/ui-components/test-setup.ts index 775bce4aa4..bb0cd5781d 100644 --- a/packages/ui-components/test-setup.ts +++ b/packages/ui-components/test-setup.ts @@ -56,36 +56,36 @@ vi.mock('$app/stores', async () => { }); vi.mock('@rainlanguage/orderbook', () => { - const DotrainOrderGui = vi.fn(); + const RaindexOrderBuilder = vi.fn(); // @ts-expect-error - static method - DotrainOrderGui.getOrderDetails = vi.fn(); + RaindexOrderBuilder.getOrderDetails = vi.fn(); // @ts-expect-error - static method - DotrainOrderGui.getDeploymentDetails = vi.fn(); - DotrainOrderGui.prototype.newWithDeployment = vi.fn(); - DotrainOrderGui.prototype.getOrderDetails = vi.fn(); - DotrainOrderGui.prototype.setVaultId = vi.fn(); - DotrainOrderGui.prototype.getTokenInfo = vi.fn(); - DotrainOrderGui.prototype.getCurrentDeployment = vi.fn(); - DotrainOrderGui.prototype.getVaultIds = vi.fn(); - DotrainOrderGui.prototype.setDeposit = vi.fn(); - DotrainOrderGui.prototype.getDeposits = vi.fn(); - DotrainOrderGui.prototype.setFieldValue = vi.fn(); - DotrainOrderGui.prototype.getFieldValue = vi.fn(); - DotrainOrderGui.prototype.getSelectTokens = vi.fn(); - DotrainOrderGui.prototype.getAllTokenInfos = vi.fn(); - DotrainOrderGui.prototype.getAllFieldDefinitions = vi.fn(); - DotrainOrderGui.prototype.isSelectTokenSet = vi.fn(); - DotrainOrderGui.prototype.setSelectToken = vi.fn(); - DotrainOrderGui.prototype.unsetSelectToken = vi.fn(); - DotrainOrderGui.prototype.hasAnyDeposit = vi.fn(); - DotrainOrderGui.prototype.hasAnyVaultId = vi.fn(); - DotrainOrderGui.prototype.areAllTokensSelected = vi.fn(); - DotrainOrderGui.prototype.getDeploymentTransactionArgs = vi.fn(); - DotrainOrderGui.prototype.generateApprovalCalldatas = vi.fn(); - DotrainOrderGui.prototype.serializeState = vi.fn(); - DotrainOrderGui.prototype.getAllGuiConfig = vi.fn(); - DotrainOrderGui.prototype.getCurrentDeploymentDetails = vi.fn(); + RaindexOrderBuilder.getDeploymentDetails = vi.fn(); + RaindexOrderBuilder.prototype.newWithDeployment = vi.fn(); + RaindexOrderBuilder.prototype.getOrderDetails = vi.fn(); + RaindexOrderBuilder.prototype.setVaultId = vi.fn(); + RaindexOrderBuilder.prototype.getTokenInfo = vi.fn(); + RaindexOrderBuilder.prototype.getCurrentDeployment = vi.fn(); + RaindexOrderBuilder.prototype.getVaultIds = vi.fn(); + RaindexOrderBuilder.prototype.setDeposit = vi.fn(); + RaindexOrderBuilder.prototype.getDeposits = vi.fn(); + RaindexOrderBuilder.prototype.setFieldValue = vi.fn(); + RaindexOrderBuilder.prototype.getFieldValue = vi.fn(); + RaindexOrderBuilder.prototype.getSelectTokens = vi.fn(); + RaindexOrderBuilder.prototype.getAllTokenInfos = vi.fn(); + RaindexOrderBuilder.prototype.getAllFieldDefinitions = vi.fn(); + RaindexOrderBuilder.prototype.isSelectTokenSet = vi.fn(); + RaindexOrderBuilder.prototype.setSelectToken = vi.fn(); + RaindexOrderBuilder.prototype.unsetSelectToken = vi.fn(); + RaindexOrderBuilder.prototype.hasAnyDeposit = vi.fn(); + RaindexOrderBuilder.prototype.hasAnyVaultId = vi.fn(); + RaindexOrderBuilder.prototype.areAllTokensSelected = vi.fn(); + RaindexOrderBuilder.prototype.getDeploymentTransactionArgs = vi.fn(); + RaindexOrderBuilder.prototype.generateApprovalCalldatas = vi.fn(); + RaindexOrderBuilder.prototype.serializeState = vi.fn(); + RaindexOrderBuilder.prototype.getAllBuilderConfig = vi.fn(); + RaindexOrderBuilder.prototype.getCurrentDeploymentDetails = vi.fn(); return { - DotrainOrderGui + RaindexOrderBuilder }; }); diff --git a/packages/webapp/ARCHITECTURE.md b/packages/webapp/ARCHITECTURE.md index 2b9934708f..18953899f0 100644 --- a/packages/webapp/ARCHITECTURE.md +++ b/packages/webapp/ARCHITECTURE.md @@ -4,7 +4,7 @@ This package is the SvelteKit web application for exploring and interacting with - Browsing orders, trades and vaults across networks - Viewing order detail and performing actions (remove, deposit, withdraw) -- Deploying “algorithmic orders” from a dotrain registry via a guided GUI +- Deploying “algorithmic orders” from a dotrain registry via a guided builder - Managing wallet connection and transaction flows @@ -66,7 +66,7 @@ nix develop -c npm run dev - Loads a dotrain registry (`?registry=` query param or default `REGISTRY_URL`), validates orders, and shows valid/invalid sections. - Nested routes: - `/deploy/[orderName]` — loads the dotrain and order details - - `/deploy/[orderName]/[deploymentKey]` — fetches deployment detail with `DotrainOrderGui.getDeploymentDetail`, then renders a GUI for composing calldata + - `/deploy/[orderName]/[deploymentKey]` — fetches deployment detail with `RaindexOrderBuilder.getDeploymentDetail`, then renders a builder for composing calldata - `/license` — Static license information. @@ -77,7 +77,7 @@ nix develop -c npm run dev - `+layout.svelte` — Provider composition and app shell (sidebar + content area). - `orders/` — Orders list and dynamic order detail. - `vaults/` — Vaults list and dynamic vault detail. - - `deploy/` — Registry load/validation, order selection, and deployment GUI routes. + - `deploy/` — Registry load/validation, order selection, and deployment builder routes. - `src/lib/` - `components/` — App‑specific wrappers (Sidebar, modals, loaders, error page, etc.). - `services/` — Side‑effectful helpers (wallet init, transactions, modal helpers, deposit/withdraw flows). diff --git a/packages/webapp/src/__tests__/handleAddOrder.test.ts b/packages/webapp/src/__tests__/handleAddOrder.test.ts index e57067ea53..49cf36d11f 100644 --- a/packages/webapp/src/__tests__/handleAddOrder.test.ts +++ b/packages/webapp/src/__tests__/handleAddOrder.test.ts @@ -3,7 +3,7 @@ import { handleAddOrder } from '../lib/services/handleAddOrder'; import type { HandleAddOrderDependencies } from '../lib/services/handleAddOrder'; import type { DeploymentTransactionArgs, - DotrainOrderGui, + RaindexOrderBuilder, RaindexClient } from '@rainlanguage/orderbook'; import type { TransactionManager } from '@rainlanguage/ui-components'; @@ -23,14 +23,14 @@ const mockManager = { createMetaTransaction: mockCreateMetaTransaction } as unknown as TransactionManager; -// New Mocks for gui +// New Mocks for builder const mockGetDeploymentTransactionArgs = vi.fn(); const MOCKED_ACCOUNT = '0xmockAccount' as Hex; -const mockGui = { +const mockBuilder = { getDeploymentTransactionArgs: mockGetDeploymentTransactionArgs -} as unknown as DotrainOrderGui; +} as unknown as RaindexOrderBuilder; const mockRaindexClient = {} as unknown as RaindexClient; @@ -38,7 +38,7 @@ const mockDeps: HandleAddOrderDependencies = { handleTransactionConfirmationModal: mockHandleTransactionConfirmationModal, errToast: mockErrToast, manager: mockManager, - gui: mockGui, + builder: mockBuilder, account: MOCKED_ACCOUNT, raindexClient: mockRaindexClient }; @@ -488,7 +488,7 @@ describe('handleAddOrder', () => { }); it('should call errToast if getDeploymentTransactionArgs returns an error', async () => { - const customErrorMsg = 'Custom error from gui'; + const customErrorMsg = 'Custom error from builder'; mockGetDeploymentTransactionArgs.mockResolvedValue({ value: null, error: { msg: customErrorMsg } diff --git a/packages/webapp/src/lib/constants.ts b/packages/webapp/src/lib/constants.ts index e499faf7f0..c26e8bd5f8 100644 --- a/packages/webapp/src/lib/constants.ts +++ b/packages/webapp/src/lib/constants.ts @@ -1,2 +1,2 @@ export const REGISTRY_URL = - 'https://raw.githubusercontent.com/rainlanguage/rain.strategies/3c5077c3900f283f28748d8b1732aa3c9bce979e/registry'; + 'https://raw.githubusercontent.com/rainlanguage/rain.strategies/b59db7f06eb24746a21968c44d4ccd368d64495c/registry'; diff --git a/packages/webapp/src/lib/services/handleAddOrder.ts b/packages/webapp/src/lib/services/handleAddOrder.ts index 0bff213eba..a69a167538 100644 --- a/packages/webapp/src/lib/services/handleAddOrder.ts +++ b/packages/webapp/src/lib/services/handleAddOrder.ts @@ -1,6 +1,6 @@ import type { Address, - DotrainOrderGui, + RaindexOrderBuilder, RaindexClient, RaindexVault, RaindexVaultToken @@ -14,7 +14,7 @@ import { type Hex } from 'viem'; export enum AddOrderErrors { ADD_ORDER_FAILED = 'Failed to add order', - MISSING_GUI = 'Order GUI is required', + MISSING_BUILDER = 'Order builder is required', MISSING_CONFIG = 'Wagmi config is required', NO_ACCOUNT_CONNECTED = 'No wallet address found', ERROR_GETTING_ARGS = 'Error getting deployment transaction args', @@ -25,19 +25,19 @@ export type HandleAddOrderDependencies = { handleTransactionConfirmationModal: HandleTransactionConfirmationModal; errToast: (message: string) => void; manager: TransactionManager; - gui: DotrainOrderGui; + builder: RaindexOrderBuilder; raindexClient: RaindexClient; account: Hex | null; }; export const handleAddOrder = async (deps: HandleAddOrderDependencies) => { - const { gui, account, errToast, raindexClient } = deps; + const { builder, account, errToast, raindexClient } = deps; if (!account) { return errToast('Could not deploy: ' + AddOrderErrors.NO_ACCOUNT_CONNECTED); } - const result = await gui.getDeploymentTransactionArgs(account); + const result = await builder.getDeploymentTransactionArgs(account); if (result.error) { return errToast('Could not deploy: ' + result.error.msg); diff --git a/packages/webapp/src/lib/services/handleUpdateGuiState.ts b/packages/webapp/src/lib/services/handleUpdateBuilderState.ts similarity index 78% rename from packages/webapp/src/lib/services/handleUpdateGuiState.ts rename to packages/webapp/src/lib/services/handleUpdateBuilderState.ts index fb55b7823a..1ea90a60ea 100644 --- a/packages/webapp/src/lib/services/handleUpdateGuiState.ts +++ b/packages/webapp/src/lib/services/handleUpdateBuilderState.ts @@ -1,7 +1,7 @@ import { pushState } from '$app/navigation'; import { debounce } from 'lodash'; -export const pushGuiStateToUrlHistory = debounce((serializedState: string) => { +export const pushBuilderStateToUrlHistory = debounce((serializedState: string) => { pushState(`?state=${serializedState}`, { serializedState }); }, 1000); @@ -13,7 +13,7 @@ if (import.meta.vitest) { pushState: vi.fn() })); - describe('handleUpdateGuiState', () => { + describe('handleUpdateBuilderState', () => { beforeEach(() => { vi.clearAllMocks(); vi.useFakeTimers(); @@ -25,7 +25,7 @@ if (import.meta.vitest) { it('should push state to URL history when serializedState exists', async () => { const mockSerializedState = 'mockSerializedState123'; - pushGuiStateToUrlHistory(mockSerializedState); + pushBuilderStateToUrlHistory(mockSerializedState); // Fast-forward timers to trigger debounced function await vi.advanceTimersByTimeAsync(1000); @@ -39,9 +39,9 @@ if (import.meta.vitest) { const mockSerializedState = 'mockSerializedState123'; // Call multiple times in quick succession - pushGuiStateToUrlHistory(mockSerializedState); - pushGuiStateToUrlHistory(mockSerializedState); - pushGuiStateToUrlHistory(mockSerializedState); + pushBuilderStateToUrlHistory(mockSerializedState); + pushBuilderStateToUrlHistory(mockSerializedState); + pushBuilderStateToUrlHistory(mockSerializedState); await vi.advanceTimersByTimeAsync(1000); diff --git a/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/+page.svelte b/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/+page.svelte index b822f9c3dc..4143c028b5 100644 --- a/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/+page.svelte +++ b/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/+page.svelte @@ -3,18 +3,18 @@ import { goto } from '$app/navigation'; import { DeploymentSteps, - GuiProvider, + RaindexOrderBuilderProvider, useAccount, useToasts, useTransactions } from '@rainlanguage/ui-components'; import { connected, appKitModal } from '$lib/stores/wagmi'; import { handleDisclaimerModal } from '$lib/services/modal'; - import { RaindexClient, DotrainOrderGui } from '@rainlanguage/orderbook'; + import { RaindexClient, RaindexOrderBuilder } from '@rainlanguage/orderbook'; import { onMount } from 'svelte'; import { handleAddOrder } from '$lib/services/handleAddOrder'; import { handleTransactionConfirmationModal } from '$lib/services/modal'; - import { pushGuiStateToUrlHistory } from '$lib/services/handleUpdateGuiState'; + import { pushBuilderStateToUrlHistory } from '$lib/services/handleUpdateBuilderState'; const { orderName, deployment, orderDetail, registry } = $page.data; const stateFromUrl = $page.url.searchParams?.get('state') || ''; @@ -23,8 +23,8 @@ const { manager } = useTransactions(); const { errToast } = useToasts(); - let gui: DotrainOrderGui | null = null; - let getGuiError: string | null = null; + let builder: RaindexOrderBuilder | null = null; + let getBuilderError: string | null = null; onMount(async () => { if (!deployment || !registry || !orderName) { @@ -35,21 +35,21 @@ } const serializedState = stateFromUrl || undefined; - const guiResult = await registry.getGui( + const builderResult = await registry.getOrderBuilder( orderName, deployment.key, serializedState, - pushGuiStateToUrlHistory + pushBuilderStateToUrlHistory ); - if (guiResult.error) { - getGuiError = guiResult.error.readableMsg ?? guiResult.error.msg; + if (builderResult.error) { + getBuilderError = builderResult.error.readableMsg ?? builderResult.error.msg; return; } - gui = guiResult.value; + builder = builderResult.value; }); - const onDeploy = (raindexClient: RaindexClient, gui: DotrainOrderGui) => { + const onDeploy = (raindexClient: RaindexClient, builder: RaindexOrderBuilder) => { handleDisclaimerModal({ open: true, onAccept: () => { @@ -58,7 +58,7 @@ handleTransactionConfirmationModal, errToast, manager, - gui, + builder, account: $account }); } @@ -68,9 +68,9 @@ {#if !deployment || !registry}
Deployment not found. Redirecting to deployments page...
-{:else if gui} -
- +{:else if builder} +
+ - +
-{:else if getGuiError} +{:else if getBuilderError}
- {getGuiError} + {getBuilderError}
{/if} diff --git a/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/fullDeployment.test.ts b/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/fullDeployment.test.ts index 945dbcf65a..77221e2d44 100644 --- a/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/fullDeployment.test.ts +++ b/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/fullDeployment.test.ts @@ -11,7 +11,7 @@ import { import { readable, writable } from 'svelte/store'; import { DotrainRegistry, - type DotrainOrderGui, + type RaindexOrderBuilder, type NameAndDescriptionCfg } from '@rainlanguage/orderbook'; import { REGISTRY_URL } from '$lib/constants'; @@ -32,13 +32,18 @@ async function createRegistry(): Promise { }); } -async function getGui( +async function getBuilder( rl: DotrainRegistry, serializedState?: string, stateCallback?: (state: string) => void -): Promise { +): Promise { return retry(async () => { - const result = await rl.getGui('fixed-limit', 'base', serializedState, stateCallback ?? null); + const result = await rl.getOrderBuilder( + 'fixed-limit', + 'base', + serializedState, + stateCallback ?? null + ); if (result.error) { throw new Error(result.error.readableMsg ?? result.error.msg); } @@ -46,23 +51,23 @@ async function getGui( }); } -async function createConfiguredGui( +async function createConfiguredBuilder( rl: DotrainRegistry, stateCallback?: (state: string) => void -): Promise { - const gui = await getGui(rl, undefined, stateCallback); - const token1Result = await gui.setSelectToken('token1', TOKEN1_ADDRESS); +): Promise { + const builder = await getBuilder(rl, undefined, stateCallback); + const token1Result = await builder.setSelectToken('token1', TOKEN1_ADDRESS); if (token1Result.error) { throw new Error('setSelectToken token1: ' + token1Result.error.msg); } - const token2Result = await gui.setSelectToken('token2', TOKEN2_ADDRESS); + const token2Result = await builder.setSelectToken('token2', TOKEN2_ADDRESS); if (token2Result.error) { throw new Error('setSelectToken token2: ' + token2Result.error.msg); } - gui.setVaultId('output', 'token2', '234'); - gui.setVaultId('input', 'token1', '123'); - gui.setFieldValue('fixed-io', '10'); - return gui; + builder.setVaultId('output', 'token2', '234'); + builder.setVaultId('input', 'token1', '123'); + builder.setFieldValue('fixed-io', '10'); + return builder; } const { mockPageStore } = await vi.hoisted(() => import('@rainlanguage/ui-components')); @@ -111,12 +116,12 @@ beforeAll(async () => { registry = await createRegistry(); }); -describe('GUI deployment args isolation tests', () => { +describe('Builder deployment args isolation tests', () => { it( - 'standalone GUI without callback produces deployment args', + 'standalone builder without callback produces deployment args', async () => { - const gui = await createConfiguredGui(registry); - const result = await gui.getDeploymentTransactionArgs(ACCOUNT); + const builder = await createConfiguredBuilder(registry); + const result = await builder.getDeploymentTransactionArgs(ACCOUNT); expect(result.error).toBeUndefined(); const args = result.value!; expect(args).toBeDefined(); @@ -128,11 +133,11 @@ describe('GUI deployment args isolation tests', () => { ); it( - 'standalone GUI with noop state callback produces deployment args', + 'standalone builder with noop state callback produces deployment args', async () => { const callback = vi.fn(); - const gui = await createConfiguredGui(registry, callback); - const result = await gui.getDeploymentTransactionArgs(ACCOUNT); + const builder = await createConfiguredBuilder(registry, callback); + const result = await builder.getDeploymentTransactionArgs(ACCOUNT); expect(result.error).toBeUndefined(); const args = result.value!; expect(args).toBeDefined(); @@ -145,8 +150,8 @@ describe('GUI deployment args isolation tests', () => { it( 'generateAddOrderCalldata works standalone', async () => { - const gui = await createConfiguredGui(registry); - const result = await gui.generateAddOrderCalldata(); + const builder = await createConfiguredBuilder(registry); + const result = await builder.generateAddOrderCalldata(); expect(result.error).toBeUndefined(); expect(result.value).toBeDefined(); }, @@ -156,8 +161,8 @@ describe('GUI deployment args isolation tests', () => { it( 'generateApprovalCalldatas works standalone', async () => { - const gui = await createConfiguredGui(registry); - const result = await gui.generateApprovalCalldatas(ACCOUNT); + const builder = await createConfiguredBuilder(registry); + const result = await builder.generateApprovalCalldatas(ACCOUNT); expect(result.error).toBeUndefined(); expect(result.value).toBeDefined(); }, @@ -167,8 +172,8 @@ describe('GUI deployment args isolation tests', () => { it( 'generateDepositCalldatas works standalone', async () => { - const gui = await createConfiguredGui(registry); - const result = await gui.generateDepositCalldatas(); + const builder = await createConfiguredBuilder(registry); + const result = await builder.generateDepositCalldatas(); expect(result.error).toBeUndefined(); expect(result.value).toBeDefined(); }, @@ -178,13 +183,13 @@ describe('GUI deployment args isolation tests', () => { it( 'serializeState and restore produce deployment args', async () => { - const gui1 = await createConfiguredGui(registry); - const serialized = gui1.serializeState(); + const builder1 = await createConfiguredBuilder(registry); + const serialized = builder1.serializeState(); expect(serialized.error).toBeUndefined(); - const gui2 = await getGui(registry, serialized.value); + const builder2 = await getBuilder(registry, serialized.value); - const result = await gui2.getDeploymentTransactionArgs(ACCOUNT); + const result = await builder2.getDeploymentTransactionArgs(ACCOUNT); expect(result.error).toBeUndefined(); const args = result.value!; expect(args).toBeDefined(); @@ -261,10 +266,10 @@ describe('Full Deployment Tests', () => { }); const screen = render(Page); - // Wait for the gui provider to be in the document + // Wait for the builder provider to be in the document await waitFor( () => { - expect(screen.getByTestId('gui-provider')).toBeInTheDocument(); + expect(screen.getByTestId('builder-provider')).toBeInTheDocument(); }, { timeout: 30000 } ); @@ -336,11 +341,28 @@ describe('Full Deployment Tests', () => { { timeout: 5000 } ); + const getDeploymentArgs = async () => { + if (!registry) { + throw new Error('Registry not initialized'); + } + const builderResult = await registry.getOrderBuilder('fixed-limit', 'base'); + if (builderResult.error) { + throw new Error(builderResult.error.readableMsg ?? builderResult.error.msg); + } + const builder = builderResult.value; + await builder.setSelectToken('token1', '0x000000000000012def132e61759048be5b5c6033'); + await builder.setSelectToken('token2', '0x00000000000007c8612ba63df8ddefd9e6077c97'); + builder.setVaultId('output', 'token2', '234'); + builder.setVaultId('input', 'token1', '123'); + builder.setFieldValue('fixed-io', '10'); + const args = await builder.getDeploymentTransactionArgs( + '0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E' + ); + return args.value; + }; await new Promise((resolve) => setTimeout(resolve, 10000)); - const gui = await createConfiguredGui(registry); - const standaloneArgs = await gui.getDeploymentTransactionArgs(ACCOUNT); - const args = standaloneArgs.value; + const args = await getDeploymentArgs(); // @ts-expect-error mock is not typed const callArgs = handleTransactionConfirmationModal.mock.calls.at(-1)?.[0] as @@ -393,10 +415,10 @@ describe('Full Deployment Tests', () => { // const screen = render(Page); - // // Wait for the gui provider to be in the document + // // Wait for the builder provider to be in the document // await waitFor( // () => { - // expect(screen.getByTestId('gui-provider')).toBeInTheDocument(); + // expect(screen.getByTestId('builder-provider')).toBeInTheDocument(); // }, // { timeout: 300000 } // ); @@ -488,19 +510,19 @@ describe('Full Deployment Tests', () => { // ); // const getDeploymentArgs = async () => { - // const gui = (await DotrainOrderGui.newWithDeployment(auctionOrder, 'base')) - // .value as DotrainOrderGui; - // await gui.setSelectToken('input', '0x000000000000012def132e61759048be5b5c6033'); - // await gui.setSelectToken('output', '0x00000000000007c8612ba63df8ddefd9e6077c97'); - // gui.setVaultId('output', 'output', '0x123'); - // gui.setVaultId('input', 'input', '0x234'); - // gui.setFieldValue('time-per-amount-epoch', '60'); - // gui.setFieldValue('amount-per-epoch', '10'); - // gui.setFieldValue('max-trade-amount', '100'); - // gui.setFieldValue('min-trade-amount', '1'); - // gui.setFieldValue('baseline', '10'); - // gui.setFieldValue('initial-io', '10'); - // const args = await gui.getDeploymentTransactionArgs( + // const builder = (await RaindexOrderBuilder.newWithDeployment(auctionOrder, 'base')) + // .value as RaindexOrderBuilder; + // await builder.setSelectToken('input', '0x000000000000012def132e61759048be5b5c6033'); + // await builder.setSelectToken('output', '0x00000000000007c8612ba63df8ddefd9e6077c97'); + // builder.setVaultId('output', 'output', '0x123'); + // builder.setVaultId('input', 'input', '0x234'); + // builder.setFieldValue('time-per-amount-epoch', '60'); + // builder.setFieldValue('amount-per-epoch', '10'); + // builder.setFieldValue('max-trade-amount', '100'); + // builder.setFieldValue('min-trade-amount', '1'); + // builder.setFieldValue('baseline', '10'); + // builder.setFieldValue('initial-io', '10'); + // const args = await builder.getDeploymentTransactionArgs( // '0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E' // ); // return args.value; @@ -555,10 +577,10 @@ describe('Full Deployment Tests', () => { // const screen = render(Page); - // // Wait for the gui provider to be in the document + // // Wait for the builder provider to be in the document // await waitFor( // () => { - // expect(screen.getByTestId('gui-provider')).toBeInTheDocument(); + // expect(screen.getByTestId('builder-provider')).toBeInTheDocument(); // }, // { timeout: 300000 } // ); @@ -636,18 +658,18 @@ describe('Full Deployment Tests', () => { // ); // const getDeploymentArgs = async () => { - // const gui = (await DotrainOrderGui.newWithDeployment(dynamicSpreadOrder, 'base')) - // .value as DotrainOrderGui; - // await gui.setSelectToken('token1', '0x000000000000012def132e61759048be5b5c6033'); - // await gui.setSelectToken('token2', '0x00000000000007c8612ba63df8ddefd9e6077c97'); - // gui.setVaultId('output', 'token2', '0x123'); - // gui.setVaultId('input', 'token1', '0x234'); - // gui.setFieldValue('amount-is-fast-exit', '1'); - // gui.setFieldValue('not-amount-is-fast-exit', '0'); - // gui.setFieldValue('initial-io', '100'); - // gui.setFieldValue('max-amount', '1000'); - // gui.setFieldValue('min-amount', '10'); - // const args = await gui.getDeploymentTransactionArgs( + // const builder = (await RaindexOrderBuilder.newWithDeployment(dynamicSpreadOrder, 'base')) + // .value as RaindexOrderBuilder; + // await builder.setSelectToken('token1', '0x000000000000012def132e61759048be5b5c6033'); + // await builder.setSelectToken('token2', '0x00000000000007c8612ba63df8ddefd9e6077c97'); + // builder.setVaultId('output', 'token2', '0x123'); + // builder.setVaultId('input', 'token1', '0x234'); + // builder.setFieldValue('amount-is-fast-exit', '1'); + // builder.setFieldValue('not-amount-is-fast-exit', '0'); + // builder.setFieldValue('initial-io', '100'); + // builder.setFieldValue('max-amount', '1000'); + // builder.setFieldValue('min-amount', '10'); + // const args = await builder.getDeploymentTransactionArgs( // '0x999999cf1046e68e36E1aA2E0E07105eDDD1f08E' // ); // return args.value; diff --git a/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/page.test.ts b/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/page.test.ts index 8efcbb74f6..b179c445a0 100644 --- a/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/page.test.ts +++ b/packages/webapp/src/routes/deploy/[orderName]/[deploymentKey]/page.test.ts @@ -49,13 +49,13 @@ vi.mock('$lib/services/handleAddOrder', () => ({ describe('DeployPage', () => { const mockRegistry = { - getGui: vi.fn() + getOrderBuilder: vi.fn() }; beforeEach(() => { vi.clearAllMocks(); mockPageStore.reset(); - mockRegistry.getGui.mockReset(); + mockRegistry.getOrderBuilder.mockReset(); vi.mocked(useAccount).mockReturnValue({ account: writable('0x123'), @@ -72,14 +72,14 @@ describe('DeployPage', () => { manager: writable({}), transactions: readable() }); - mockRegistry.getGui.mockResolvedValue({ value: {} }); + mockRegistry.getOrderBuilder.mockResolvedValue({ value: {} }); }); afterEach(() => { vi.resetAllMocks(); }); - it('should call registry.getGui with correct parameters when data exists', async () => { + it('should call registry.getOrderBuilder with correct parameters when data exists', async () => { const mockDeploymentKey = 'test-key'; const mockStateFromUrl = 'some-state'; @@ -96,7 +96,7 @@ describe('DeployPage', () => { render(DeployPage); await vi.waitFor(() => { - expect(mockRegistry.getGui).toHaveBeenCalledWith( + expect(mockRegistry.getOrderBuilder).toHaveBeenCalledWith( 'order-one', mockDeploymentKey, mockStateFromUrl, @@ -131,10 +131,10 @@ describe('DeployPage', () => { vi.useRealTimers(); }); - it('should show error message when GUI initialization fails', async () => { - const errorMessage = 'Failed to initialize GUI'; + it('should show error message when builder initialization fails', async () => { + const errorMessage = 'Failed to initialize builder'; - mockRegistry.getGui.mockResolvedValue({ + mockRegistry.getOrderBuilder.mockResolvedValue({ error: { readableMsg: errorMessage } }); diff --git a/pointers.sh b/pointers.sh index 15c8d6754e..17325c5d0a 100755 --- a/pointers.sh +++ b/pointers.sh @@ -6,7 +6,7 @@ set -euxo pipefail (cd lib/rain.interpreter/lib/rain.interpreter.interface/lib/rain.math.float && nix develop -c rainix-rs-prelude) (cd lib/rain.interpreter && nix develop -c rainix-sol-prelude) (cd lib/rain.interpreter && nix develop -c rainix-rs-prelude) -(cd lib/rain.interpreter && nix develop -c i9r-prelude) +(cd lib/rain.interpreter && nix develop -c rainlang-prelude) (cd lib/rain.interpreter/lib/rain.metadata && nix develop -c rainix-sol-prelude) (cd lib/rain.interpreter/lib/rain.metadata && nix develop -c rainix-rs-prelude) (cd lib/rain.interpreter/lib/rain.tofu.erc20-decimals && nix develop -c forge build) @@ -15,4 +15,4 @@ nix develop -c rainix-sol-prelude nix develop -c rainix-rs-prelude nix develop -c raindex-prelude -nix develop -c forge script script/BuildPointers.sol \ No newline at end of file +nix develop -c forge script script/BuildPointers.sol diff --git a/prep-base.sh b/prep-base.sh index 1bb9fda56e..e6c5469530 100755 --- a/prep-base.sh +++ b/prep-base.sh @@ -46,7 +46,7 @@ echo "Setting up rain.tofu.erc20-decimals..." echo "Setting up rain.interpreter..." nix develop -i ${keep[@]} -c bash -c '(cd lib/rain.interpreter && rainix-sol-prelude)' nix develop -i ${keep[@]} -c bash -c '(cd lib/rain.interpreter && rainix-rs-prelude)' -(cd lib/rain.interpreter && nix develop -i ${keep[@]} -c bash -c i9r-prelude) +(cd lib/rain.interpreter && nix develop -i ${keep[@]} -c bash -c rainlang-prelude) echo "Setting up rain.metadata..." nix develop -i ${keep[@]} -c bash -c '(cd lib/rain.interpreter/lib/rain.metadata && rainix-sol-prelude)' diff --git a/src/concrete/parser/OrderBookV6SubParser.sol b/src/concrete/parser/OrderBookV6SubParser.sol index 9bade6da61..9fa42d1bb1 100644 --- a/src/concrete/parser/OrderBookV6SubParser.sol +++ b/src/concrete/parser/OrderBookV6SubParser.sol @@ -4,10 +4,10 @@ pragma solidity =0.8.25; import { LibParseOperand, - BaseRainterpreterSubParser, + BaseRainlangSubParser, OperandV2, IParserToolingV1 -} from "rain.interpreter/abstract/BaseRainterpreterSubParser.sol"; +} from "rain.interpreter/abstract/BaseRainlangSubParser.sol"; import {LibConvert} from "rain.lib.typecast/LibConvert.sol"; import {LibUint256Matrix} from "rain.solmem/lib/LibUint256Matrix.sol"; @@ -73,7 +73,7 @@ import {ISubParserToolingV1} from "rain.sol.codegen/interface/ISubParserToolingV /// @notice Sub-parser that provides orderbook-specific context words (sender, /// order hash, vault IO, deposit/withdraw, signed context, etc.) to the /// Rain interpreter. -contract OrderBookV6SubParser is BaseRainterpreterSubParser { +contract OrderBookV6SubParser is BaseRainlangSubParser { using LibUint256Matrix for uint256[][]; /// @inheritdoc IDescribedByMetaV1 @@ -81,17 +81,17 @@ contract OrderBookV6SubParser is BaseRainterpreterSubParser { return DESCRIBED_BY_META_HASH; } - /// @inheritdoc BaseRainterpreterSubParser + /// @inheritdoc BaseRainlangSubParser function subParserParseMeta() internal pure virtual override returns (bytes memory) { return SUB_PARSER_PARSE_META; } - /// @inheritdoc BaseRainterpreterSubParser + /// @inheritdoc BaseRainlangSubParser function subParserWordParsers() internal pure virtual override returns (bytes memory) { return SUB_PARSER_WORD_PARSERS; } - /// @inheritdoc BaseRainterpreterSubParser + /// @inheritdoc BaseRainlangSubParser function subParserOperandHandlers() internal pure virtual override returns (bytes memory) { return SUB_PARSER_OPERAND_HANDLERS; }