Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions crates/common/src/erc20.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ pub struct TokenInfo {
#[cfg(target_family = "wasm")]
impl_wasm_traits!(TokenInfo);

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(target_family = "wasm", derive(Tsify))]
pub struct TokenInfoExtended {
pub key: String,
#[cfg_attr(target_family = "wasm", tsify(type = "string"))]
pub address: Address,
pub decimals: u8,
pub name: String,
pub symbol: String,
pub chain_id: u32,
}
#[cfg(target_family = "wasm")]
impl_wasm_traits!(TokenInfoExtended);

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ERC20 {
pub rpcs: Vec<Url>,
Expand Down
4 changes: 3 additions & 1 deletion crates/js_api/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,16 @@
- `getAllOrderDetails()` → parse order-level metadata for every merged dotrain.
- `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.

- `yaml` (src/yaml/mod.rs)
- Purpose: Wasm-friendly wrapper around orderbook YAML parsing to retrieve an `OrderbookCfg` by contract address.
- Purpose: Wasm-friendly wrapper around orderbook YAML parsing to retrieve configuration objects by address or query token metadata.
- Exports:
- `OrderbookYaml.new([yamlSources], validate?) -> OrderbookYaml`: parse/merge/optionally validate sources.
- `OrderbookYaml.getOrderbookByAddress(address) -> OrderbookCfg`.
- `OrderbookYaml.getTokens() -> TokenInfo[]` (async): returns all tokens from YAML with `chain_id`, `address`, `decimals`, `symbol`, and `name`. Automatically fetches remote tokens from `using-tokens-from` URLs.
- Errors: `OrderbookYamlError` with readable messaging, converted to JS.

**External Crates & Interactions**
Expand Down
27 changes: 10 additions & 17 deletions crates/js_api/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use rain_orderbook_app_settings::{
YamlError, YamlParsable,
},
};
pub use rain_orderbook_common::erc20::TokenInfoExtended;
use rain_orderbook_common::{
dotrain::{types::patterns::FRONTMATTER_SEPARATOR, RainDocument},
dotrain_order::{DotrainOrder, DotrainOrderError},
Expand All @@ -39,16 +40,6 @@ mod select_tokens;
mod state_management;
mod validation;

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Tsify)]
pub struct TokenInfo {
pub key: String,
#[tsify(type = "string")]
pub address: Address,
pub decimals: u8,
pub name: String,
pub symbol: String,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[wasm_bindgen]
pub struct DotrainOrderGui {
Expand Down Expand Up @@ -278,25 +269,26 @@ impl DotrainOrderGui {
/// ```
#[wasm_export(
js_name = "getTokenInfo",
unchecked_return_type = "TokenInfo",
return_description = "Complete token details including address, decimals, name, and symbol"
unchecked_return_type = "TokenInfoExtended",
return_description = "Complete token details including address, decimals, name, symbol, and chain_id"
)]
pub async fn get_token_info(
&self,
#[wasm_export(param_description = "Token identifier from the YAML tokens section")]
key: String,
) -> Result<TokenInfo, GuiError> {
) -> Result<TokenInfoExtended, GuiError> {
let token = self.dotrain_order.orderbook_yaml().get_token(&key)?;

let token_info = if let (Some(decimals), Some(label), Some(symbol)) =
(&token.decimals, &token.label, &token.symbol)
{
TokenInfo {
TokenInfoExtended {
key: token.key.clone(),
address: token.address,
decimals: *decimals,
name: label.clone(),
symbol: symbol.clone(),
chain_id: token.network.chain_id,
}
} else {
let order_key = DeploymentCfg::parse_order_key(
Expand All @@ -313,12 +305,13 @@ impl DotrainOrderGui {
let erc20 = ERC20::new(rpcs, token.address);
let onchain_info = erc20.token_info(None).await?;

TokenInfo {
TokenInfoExtended {
key: token.key.clone(),
address: token.address,
decimals: token.decimals.unwrap_or(onchain_info.decimals),
name: token.label.unwrap_or(onchain_info.name),
symbol: token.symbol.unwrap_or(onchain_info.symbol),
chain_id: token.network.chain_id,
}
};

Expand Down Expand Up @@ -349,10 +342,10 @@ impl DotrainOrderGui {
/// ```
#[wasm_export(
js_name = "getAllTokenInfos",
unchecked_return_type = "TokenInfo[]",
unchecked_return_type = "TokenInfoExtended[]",
return_description = "Array of complete token information"
)]
pub async fn get_all_token_infos(&self) -> Result<Vec<TokenInfo>, GuiError> {
pub async fn get_all_token_infos(&self) -> Result<Vec<TokenInfoExtended>, GuiError> {
let select_tokens = self.get_select_tokens()?;

let token_keys = match select_tokens.is_empty() {
Expand Down
13 changes: 8 additions & 5 deletions crates/js_api/src/gui/select_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ impl DotrainOrderGui {
/// ```
#[wasm_export(
js_name = "getAllTokens",
unchecked_return_type = "TokenInfo[]",
unchecked_return_type = "TokenInfoExtended[]",
return_description = "Array of token information for the current network"
)]
pub async fn get_all_tokens(
Expand All @@ -285,7 +285,7 @@ impl DotrainOrderGui {
param_description = "Optional search term to filter tokens by name, symbol, or address"
)]
search: Option<String>,
) -> Result<Vec<TokenInfo>, GuiError> {
) -> Result<Vec<TokenInfoExtended>, GuiError> {
let order_key = DeploymentCfg::parse_order_key(
self.dotrain_order.dotrain_yaml().documents,
&self.selected_deployment,
Expand All @@ -308,29 +308,32 @@ impl DotrainOrderGui {
if let (Some(decimals), Some(label), Some(symbol)) =
(&token.decimals, &token.label, &token.symbol)
{
results.push(TokenInfo {
results.push(TokenInfoExtended {
key: token.key.clone(),
address: token.address,
decimals: *decimals,
name: label.clone(),
symbol: symbol.clone(),
chain_id: token.network.chain_id,
});
} else {
let chain_id = token.network.chain_id;
let erc20 = ERC20::new(network.rpcs.clone(), token.address);
fetch_futures.push(async move {
let token_info = erc20.token_info(None).await?;
Ok::<TokenInfo, GuiError>(TokenInfo {
Ok::<TokenInfoExtended, GuiError>(TokenInfoExtended {
key: token.key.clone(),
address: token.address,
decimals: token.decimals.unwrap_or(token_info.decimals),
name: token.label.unwrap_or(token_info.name),
symbol: token.symbol.unwrap_or(token_info.symbol),
chain_id,
})
});
}
}

let fetched_results: Vec<TokenInfo> = futures::stream::iter(fetch_futures)
let fetched_results: Vec<TokenInfoExtended> = futures::stream::iter(fetch_futures)
.buffer_unordered(MAX_CONCURRENT_FETCHES)
.filter_map(|res| async {
match res {
Expand Down
133 changes: 132 additions & 1 deletion crates/js_api/src/registry.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::gui::{DotrainOrderGui, GuiError};
use crate::yaml::{OrderbookYaml, OrderbookYamlError};
use rain_orderbook_app_settings::gui::NameAndDescriptionCfg;
use reqwest;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -133,6 +134,8 @@ pub enum DotrainRegistryError {
UrlParseError(#[from] url::ParseError),
#[error(transparent)]
GuiError(#[from] GuiError),
#[error(transparent)]
OrderbookYamlError(#[from] OrderbookYamlError),
}

impl DotrainRegistryError {
Expand Down Expand Up @@ -162,7 +165,8 @@ 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::GuiError(err) => err.to_readable_msg(),
DotrainRegistryError::OrderbookYamlError(err) => err.to_readable_msg(),
}
}
}
Expand Down Expand Up @@ -458,6 +462,32 @@ impl DotrainRegistry {
let gui = gui_result.map_err(DotrainRegistryError::GuiError)?;
Ok(gui)
}

/// Creates an OrderbookYaml instance from the registry's shared settings.
///
/// This method provides access to the OrderbookYaml SDK, allowing you to query tokens,
/// networks, orderbooks, and other configuration from the shared settings YAML.
///
/// ## Examples
///
/// ```javascript
/// const yamlResult = registry.getOrderbookYaml();
/// if (yamlResult.error) {
/// console.error("Failed to get OrderbookYaml:", yamlResult.error.readableMsg);
/// return;
/// }
/// const orderbookYaml = yamlResult.value;
/// ```
#[wasm_export(
js_name = "getOrderbookYaml",
preserve_js_class,
unchecked_return_type = "OrderbookYaml",
return_description = "OrderbookYaml instance from registry settings"
)]
pub fn get_orderbook_yaml(&self) -> Result<OrderbookYaml, DotrainRegistryError> {
let yaml = OrderbookYaml::new(vec![self.settings.clone()], None)?;
Ok(yaml)
}
}

impl DotrainRegistry {
Expand Down Expand Up @@ -1401,5 +1431,106 @@ _ _: 1 1;
_ => panic!("Expected OrderKeyNotFound error"),
}
}

const MOCK_SETTINGS_WITH_TOKENS: &str = r#"version: 4
networks:
mainnet:
rpcs:
- https://mainnet.infura.io
chain-id: 1
currency: ETH
tokens:
weth:
network: mainnet
address: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
decimals: 18
label: Wrapped Ether
symbol: WETH
usdc:
network: mainnet
address: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
decimals: 6
label: USD Coin
symbol: USDC
orderbooks:
mainnet:
address: 0x1234567890123456789012345678901234567890
network: mainnet
deployers:
mainnet:
address: 0x1234567890123456789012345678901234567890
network: mainnet
"#;

const MOCK_DOTRAIN_SIMPLE: &str = r#"gui:
name: Test Order
description: Test description
deployments:
mainnet:
name: Mainnet Order
description: Mainnet deployment
deposits:
- token: weth
presets:
- "0"
fields:
- binding: test-binding
name: Test binding
presets:
- value: "0xbeef"
scenarios:
mainnet:
deployer: mainnet
runs: 1
orders:
mainnet:
orderbook: mainnet
inputs:
- token: weth
outputs:
- token: usdc
deployments:
mainnet:
scenario: mainnet
order: mainnet
---
#calculate-io
_ _: 0 0;
#handle-io
:;
"#;

#[tokio::test]
async fn test_get_orderbook_yaml_returns_valid_instance() {
let server = MockServer::start_async().await;

let test_registry_content = format!(
"{}/settings.yaml\ntest-order {}/order.rain",
server.url(""),
server.url("")
);

server.mock(|when, then| {
when.method("GET").path("/registry.txt");
then.status(200).body(test_registry_content.clone());
});

server.mock(|when, then| {
when.method("GET").path("/settings.yaml");
then.status(200).body(MOCK_SETTINGS_WITH_TOKENS);
});

server.mock(|when, then| {
when.method("GET").path("/order.rain");
then.status(200).body(MOCK_DOTRAIN_SIMPLE);
});

let registry = DotrainRegistry::new(format!("{}/registry.txt", server.url("")))
.await
.unwrap();

let orderbook_yaml = registry.get_orderbook_yaml();
assert!(orderbook_yaml.is_ok());
}
}
}
Loading
Loading