Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e9f2afb
feat: add SignedContextOracleV1 to ParsedMeta and expose oracle_url o…
Feb 13, 2026
f0e1adf
fix: add tests, non-wasm oracle_url(), address review feedback
Feb 15, 2026
f2566dc
chore: update rain.interpreter submodule to include SignedContextOrac…
Feb 15, 2026
d4339cb
rename SignedContextOracleV1 → RaindexSignedContextOracleV1
Feb 17, 2026
263d233
chore: bump rain.interpreter to include metadata rename
Feb 17, 2026
faa6fdc
fix: cargo fmt parsed_meta.rs
Feb 22, 2026
63e3282
fix: update rain.interpreter submodule to include metadata fmt fix
Feb 22, 2026
f29d775
feat: add oracle fetch module and wire signed context into take-order…
Feb 15, 2026
7f98109
feat: wire oracle signed context into quote flow
Feb 15, 2026
1609cc1
fix: clippy lints and formatting
Feb 15, 2026
c1391fe
fix: conditionally apply reqwest timeout for non-WASM targets
Feb 15, 2026
f7e519b
feat: show oracle info on order detail page
Feb 15, 2026
f628b79
revert: remove frontend changes from Phase 3 PR
Feb 15, 2026
0275c3a
switch oracle fetch to POST with ABI-encoded body
Feb 17, 2026
026a106
oracle fetch: body is required, no GET fallback
Feb 17, 2026
aec3e74
wire ABI-encoded per-pair oracle fetches
Feb 17, 2026
63ea51f
refactor: move oracle into quote crate, remove closure pattern
Feb 17, 2026
2ac0d9b
fmt: cargo fmt fixes
Feb 23, 2026
d5f6698
feat: update oracle to batch format
Feb 23, 2026
5963780
fix: prettier formatting
Feb 15, 2026
b8e8baa
feat: parse oracle-url from YAML order config (Phase 6)
Feb 16, 2026
3266868
feat: encode oracle-url into order metadata (Phase 7)
Feb 16, 2026
3e96b73
propagate RaindexSignedContextOracleV1 rename to add_order.rs
Feb 17, 2026
4d23ec9
fix: cargo fmt formatting
Feb 23, 2026
dd752c5
Add extractOracleUrl static method to RaindexOrder
Feb 23, 2026
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
24 changes: 22 additions & 2 deletions crates/common/src/add_order.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@ use rain_interpreter_eval::{
};
use rain_interpreter_parser::{Parser2, ParserError, ParserV2};
use rain_metadata::{
types::dotrain::gui_state_v1::DotrainGuiStateV1, ContentEncoding, ContentLanguage, ContentType,
Error as RainMetaError, KnownMagic, RainMetaDocumentV1Item,
types::dotrain::gui_state_v1::DotrainGuiStateV1,
types::raindex_signed_context_oracle::RaindexSignedContextOracleV1, ContentEncoding,
ContentLanguage, ContentType, Error as RainMetaError, KnownMagic, RainMetaDocumentV1Item,
};
use rain_metadata_bindings::MetaBoard::emitMetaCall;
use rain_orderbook_app_settings::deployment::DeploymentCfg;
Expand Down Expand Up @@ -130,6 +131,21 @@ impl AddOrderArgs {
});
}

// If the order has an oracle URL, add a RaindexSignedContextOracleV1 meta item
let additional_meta = {
let mut meta = additional_meta.unwrap_or_default();
if let Some(ref oracle_url) = deployment.order.oracle_url {
let oracle = RaindexSignedContextOracleV1::parse(oracle_url)
.map_err(AddOrderArgsError::RainMetaError)?;
meta.push(oracle.to_meta_item());
}
if meta.is_empty() {
None
} else {
Some(meta)
}
Comment on lines +134 to +146
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Deduplicate oracle metadata when the deployment already specifies an oracle URL.

new_from_deployment() now blindly appends a RaindexSignedContextOracleV1 item. If additional_meta already contains one, downstream extractors use find_in_items(), so the effective URL becomes order-dependent instead of letting deployment.order.oracle_url win.

💡 Proposed fix
         let mut meta = additional_meta.unwrap_or_default();
         if let Some(ref oracle_url) = deployment.order.oracle_url {
+            meta.retain(|item| item.magic != KnownMagic::RaindexSignedContextOracleV1);
             let oracle = RaindexSignedContextOracleV1::parse(oracle_url)
                 .map_err(AddOrderArgsError::RainMetaError)?;
             meta.push(oracle.to_meta_item());
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/common/src/add_order.rs` around lines 134 - 146, new_from_deployment()
currently always appends a RaindexSignedContextOracleV1 meta item even if
additional_meta already contains one; update the logic to deduplicate by
checking existing meta items (the vector in additional_meta) for an existing
RaindexSignedContextOracleV1 (the same meta key used by find_in_items()) and
either replace that existing item with the one parsed from
deployment.order.oracle_url or remove any existing RaindexSignedContextOracleV1
entries before pushing the new to_meta_item() returned by
RaindexSignedContextOracleV1::parse; ensure you still return None when the
resulting meta vector is empty.

};

Ok(AddOrderArgs {
dotrain: dotrain.to_string(),
inputs,
Expand Down Expand Up @@ -683,6 +699,7 @@ price: 2e18;
network: network_arc.clone(),
deployer: None,
orderbook: None,
oracle_url: None,
};
let deployment = DeploymentCfg {
document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))),
Expand Down Expand Up @@ -803,6 +820,7 @@ _ _: 0 0;
network: network_arc.clone(),
deployer: None,
orderbook: None,
oracle_url: None,
};
let deployment = DeploymentCfg {
document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))),
Expand Down Expand Up @@ -966,6 +984,7 @@ _ _: 0 0;
network: network_arc.clone(),
deployer: None,
orderbook: None,
oracle_url: None,
};
let deployment = DeploymentCfg {
document: Arc::new(RwLock::new(StrictYaml::String("".to_string()))),
Expand Down Expand Up @@ -1307,6 +1326,7 @@ _ _: 16 52;
network: network_arc.clone(),
deployer: None,
orderbook: None,
oracle_url: None,
};
DeploymentCfg {
document: default_document(),
Expand Down
1 change: 1 addition & 0 deletions crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ pub mod erc20;
pub mod fuzz;
pub mod local_db;
pub mod meta;
pub mod oracle;
pub mod parsed_meta;
pub mod raindex_client;
pub mod rainlang;
Expand Down
3 changes: 3 additions & 0 deletions crates/common/src/oracle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Re-export oracle types and functions from the quote crate.
// This maintains backward compatibility for code in common that uses oracle functionality.
pub use rain_orderbook_quote::oracle::*;
73 changes: 72 additions & 1 deletion crates/common/src/parsed_meta.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use rain_metadata::{
types::dotrain::{gui_state_v1::DotrainGuiStateV1, source_v1::DotrainSourceV1},
types::{
dotrain::{gui_state_v1::DotrainGuiStateV1, source_v1::DotrainSourceV1},
raindex_signed_context_oracle::RaindexSignedContextOracleV1,
},
KnownMagic, RainMetaDocumentV1Item,
};
use serde::{Deserialize, Serialize};
Expand All @@ -13,6 +16,7 @@ use wasm_bindgen_utils::{impl_wasm_traits, prelude::*};
pub enum ParsedMeta {
DotrainGuiStateV1(DotrainGuiStateV1),
DotrainSourceV1(DotrainSourceV1),
RaindexSignedContextOracleV1(RaindexSignedContextOracleV1),
}
#[cfg(target_family = "wasm")]
impl_wasm_traits!(ParsedMeta);
Expand All @@ -34,6 +38,10 @@ impl ParsedMeta {
let source = DotrainSourceV1::try_from(item.clone())?;
Ok(Some(ParsedMeta::DotrainSourceV1(source)))
}
KnownMagic::RaindexSignedContextOracleV1 => {
let oracle = RaindexSignedContextOracleV1::try_from(item.clone())?;
Ok(Some(ParsedMeta::RaindexSignedContextOracleV1(oracle)))
}
// Filter out all other metadata types - they're not needed for the frontend
_ => Ok(None),
}
Expand Down Expand Up @@ -198,4 +206,67 @@ mod tests {
assert!(matches!(&parsed[0], ParsedMeta::DotrainSourceV1(s) if s.0 == source.0));
assert!(matches!(&parsed[1], ParsedMeta::DotrainGuiStateV1(g) if g == &gui));
}

#[test]
fn test_from_meta_item_raindex_signed_context_oracle_v1() {
let oracle =
RaindexSignedContextOracleV1::parse("https://oracle.example.com/prices/eth-usd")
.unwrap();
let item = oracle.to_meta_item();
let result = ParsedMeta::from_meta_item(&item).unwrap();
match result.unwrap() {
ParsedMeta::RaindexSignedContextOracleV1(parsed_oracle) => {
assert_eq!(
parsed_oracle.url(),
"https://oracle.example.com/prices/eth-usd"
);
}
_ => panic!("Expected RaindexSignedContextOracleV1"),
}
}

#[test]
fn test_parse_multiple_with_oracle() {
let source = get_default_dotrain_source();
let oracle =
RaindexSignedContextOracleV1::parse("https://oracle.example.com/feed").unwrap();

let items = vec![
RainMetaDocumentV1Item::from(source.clone()),
oracle.to_meta_item(),
];

let results = ParsedMeta::parse_multiple(&items).unwrap();
assert_eq!(results.len(), 2);

match &results[0] {
ParsedMeta::DotrainSourceV1(parsed_source) => {
assert_eq!(parsed_source.0, source.0);
}
_ => panic!("Expected DotrainSourceV1"),
}

match &results[1] {
ParsedMeta::RaindexSignedContextOracleV1(parsed_oracle) => {
assert_eq!(parsed_oracle.url(), "https://oracle.example.com/feed");
}
_ => panic!("Expected RaindexSignedContextOracleV1"),
}
}

#[test]
fn test_parse_from_bytes_with_oracle() {
let oracle =
RaindexSignedContextOracleV1::parse("https://oracle.example.com/prices/eth-usd")
.unwrap();
let items = vec![oracle.to_meta_item()];
let bytes = RainMetaDocumentV1Item::cbor_encode_seq(&items, KnownMagic::RainMetaDocumentV1)
.unwrap();

let parsed = ParsedMeta::parse_from_bytes(&bytes).unwrap();
assert_eq!(parsed.len(), 1);
assert!(
matches!(&parsed[0], ParsedMeta::RaindexSignedContextOracleV1(o) if o.url() == "https://oracle.example.com/prices/eth-usd")
);
}
}
4 changes: 3 additions & 1 deletion crates/common/src/raindex_client/order_quotes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,10 @@ impl RaindexOrder {
) -> Result<Vec<RaindexOrderQuote>, RaindexError> {
let gas_amount = gas.map(|v| v.parse::<u64>()).transpose()?;
let rpcs = self.get_rpc_urls()?;
let sg_order = self.clone().into_sg_order()?;

let order_quotes = get_order_quotes(
vec![self.clone().into_sg_order()?],
vec![sg_order],
block_number,
rpcs.iter().map(|s| s.to_string()).collect(),
gas_amount,
Expand Down
39 changes: 39 additions & 0 deletions crates/common/src/raindex_client/orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,14 @@ impl RaindexOrder {
_ => None,
})
}
/// Returns the signed context oracle URL if this order has oracle metadata.
#[wasm_bindgen(getter = oracleUrl)]
pub fn oracle_url(&self) -> Option<String> {
self.parsed_meta().into_iter().find_map(|meta| match meta {
ParsedMeta::RaindexSignedContextOracleV1(oracle) => Some(oracle.0),
_ => None,
})
}
#[wasm_bindgen(getter)]
pub fn transaction(&self) -> Option<RaindexTransaction> {
self.transaction.clone()
Expand Down Expand Up @@ -300,6 +308,16 @@ impl RaindexOrder {
pub fn inputs_outputs_list(&self) -> RaindexVaultsList {
RaindexVaultsList::new(get_io_by_type(self, RaindexVaultType::InputOutput))
}

/// Extract oracle URL from raw meta bytes.
///
/// Takes raw meta bytes, CBOR decodes them as RainMetaDocumentV1Items,
/// and finds RaindexSignedContextOracleV1 by magic number.
/// Returns the oracle URL if found, None otherwise.
#[wasm_bindgen(js_name = extractOracleUrl)]
pub fn extract_oracle_url_wasm(meta_bytes: &[u8]) -> Option<String> {
RaindexOrder::extract_oracle_url(meta_bytes)
Comment on lines +317 to +319
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C2 'extract_oracle_url(_wasm)?' crates/common/src/raindex_client/orders.rs
rg -n -C1 '#\[cfg\(target_family = "wasm"\)\]|#\[cfg\(not\(target_family = "wasm"\)\)\]' \
  crates/common/src/raindex_client/orders.rs

Repository: rainlanguage/rain.orderbook

Length of output: 2116


Fix the wasm-only call to a non-wasm helper.

Line 319 calls RaindexOrder::extract_oracle_url(meta_bytes) from the wasm impl block, but the helper is only defined under #[cfg(not(target_family = "wasm"))] at line 403. This causes wasm target compilation to fail because the function is not available. Move extract_oracle_url into an unconditional impl RaindexOrder block so both wasm and non-wasm targets can access it.

🧰 Tools
🪛 GitHub Actions: GitHub Actions Vercel Docs Preview Deployment

[error] 319-319: RaindexOrder::extract_oracle_url not found. The code calls an associated function 'extract_oracle_url', but only 'extract_oracle_url_wasm' (or other variants) may exist. Consider using the correct function name or add the missing function.

🪛 GitHub Actions: GitHub Actions Vercel Preview Deployment

[error] 319-319: RaindexOrder::extract_oracle_url(meta_bytes) not found for struct RaindexOrder. Use associated function RaindexOrder::extract_oracle_url_wasm(meta_bytes) or ensure the correct method exists in this version.

🪛 GitHub Actions: Rainix CI

[error] 319-319: no function or associated item named extract_oracle_url found for struct RaindexOrder in the current scope. Consider using RaindexOrder::extract_oracle_url_wasm as an alternative, or ensure extract_oracle_url is defined for this type.

🪛 GitHub Actions: Test ui-components

[error] 319-319: RaindexOrder::extract_oracle_url not found. The log suggests a similarly named function (extract_oracle_url_wasm) may exist. This indicates a API/API-name mismatch or refactor. Update call to a valid associated function.

🪛 GitHub Actions: Test webapp

[error] 319-319: RaindexOrder::extract_oracle_url(meta_bytes) not found. Suggests using RaindexOrder::extract_oracle_url_wasm(meta_bytes) or other existing associated constructors.


[error] 319-319: Compilation failed due to the above error while building rain_orderbook_common (lib).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/common/src/raindex_client/orders.rs` around lines 317 - 319, The wasm
impl calls RaindexOrder::extract_oracle_url from extract_oracle_url_wasm, but
extract_oracle_url is currently defined only under #[cfg(not(target_family =
"wasm"))]; move the extract_oracle_url helper out of the cfg-gated block into an
unconditional impl RaindexOrder so both wasm and non-wasm builds can access it.
Concretely, create or expand an impl RaindexOrder (non-#[cfg]) and place the
extract_oracle_url function there (keeping its signature and behavior), then
ensure extract_oracle_url_wasm continues to call
RaindexOrder::extract_oracle_url.

}
}
#[cfg(not(target_family = "wasm"))]
impl RaindexOrder {
Expand Down Expand Up @@ -348,6 +366,13 @@ impl RaindexOrder {
_ => None,
})
}
/// Returns the signed context oracle URL if this order has oracle metadata.
pub fn oracle_url(&self) -> Option<String> {
self.parsed_meta().into_iter().find_map(|meta| match meta {
ParsedMeta::RaindexSignedContextOracleV1(oracle) => Some(oracle.0),
_ => None,
})
}
pub fn transaction(&self) -> Option<RaindexTransaction> {
self.transaction.clone()
}
Expand All @@ -369,6 +394,19 @@ impl RaindexOrder {
pub fn inputs_outputs_list(&self) -> RaindexVaultsList {
RaindexVaultsList::new(get_io_by_type(self, RaindexVaultType::InputOutput))
}

/// Extract oracle URL from raw meta bytes.
///
/// Takes raw meta bytes, CBOR decodes them as RainMetaDocumentV1Items,
/// and finds RaindexSignedContextOracleV1 by magic number.
/// Returns the oracle URL if found, None otherwise.
pub fn extract_oracle_url(meta_bytes: &[u8]) -> Option<String> {
use rain_metadata::{types::raindex_signed_context_oracle::RaindexSignedContextOracleV1, RainMetaDocumentV1Item};

let items = RainMetaDocumentV1Item::cbor_decode(meta_bytes).ok()?;
let oracle = RaindexSignedContextOracleV1::find_in_items(&items).ok()??;
Some(oracle.url().to_string())
}
}

fn get_vaults_with_type(
Expand Down Expand Up @@ -661,6 +699,7 @@ impl RaindexOrder {
&rpc_urls,
Some(block_number),
sell_token,
self.oracle_url(),
)
.await
}
Expand Down
20 changes: 20 additions & 0 deletions crates/common/src/raindex_client/take_orders/single.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub fn build_candidate_from_quote(
output_io_index,
max_output: data.max_output,
ratio: data.ratio,
signed_context: vec![],
}))
}

Expand Down Expand Up @@ -102,6 +103,7 @@ pub fn estimate_take_order(
))
}

#[allow(clippy::too_many_arguments)]
pub async fn execute_single_take(
candidate: TakeOrderCandidate,
mode: ParsedTakeOrdersMode,
Expand All @@ -110,7 +112,25 @@ pub async fn execute_single_take(
rpc_urls: &[Url],
block_number: Option<u64>,
sell_token: Address,
oracle_url: Option<String>,
) -> Result<TakeOrdersCalldataResult, RaindexError> {
// Fetch signed context from oracle if URL provided
let mut candidate = candidate;
if let Some(url) = oracle_url {
let body = crate::oracle::encode_oracle_body(
&candidate.order,
candidate.input_io_index,
candidate.output_io_index,
taker,
);
match crate::oracle::fetch_signed_context(&url, body).await {
Ok(ctx) => candidate.signed_context = vec![ctx],
Err(e) => {
tracing::warn!("Failed to fetch oracle data from {}: {}", url, e);
}
Comment on lines +119 to +130
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don't emit raw oracle URLs in warning logs.

Oracle endpoints can carry credentials or signed query params, and this branch logs the URL verbatim on every fetch failure. Avoid logging the raw URL here; even the formatted error can repeat it.

💡 Proposed fix
         match crate::oracle::fetch_signed_context(&url, body).await {
             Ok(ctx) => candidate.signed_context = vec![ctx],
-            Err(e) => {
-                tracing::warn!("Failed to fetch oracle data from {}: {}", url, e);
-            }
+            Err(_) => {
+                tracing::warn!("Failed to fetch oracle data");
+            }
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if let Some(url) = oracle_url {
let body = crate::oracle::encode_oracle_body(
&candidate.order,
candidate.input_io_index,
candidate.output_io_index,
taker,
);
match crate::oracle::fetch_signed_context(&url, body).await {
Ok(ctx) => candidate.signed_context = vec![ctx],
Err(e) => {
tracing::warn!("Failed to fetch oracle data from {}: {}", url, e);
}
if let Some(url) = oracle_url {
let body = crate::oracle::encode_oracle_body(
&candidate.order,
candidate.input_io_index,
candidate.output_io_index,
taker,
);
match crate::oracle::fetch_signed_context(&url, body).await {
Ok(ctx) => candidate.signed_context = vec![ctx],
Err(_) => {
tracing::warn!("Failed to fetch oracle data");
}
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/common/src/raindex_client/take_orders/single.rs` around lines 119 -
130, The warning currently logs the raw oracle_url in the fetch error branch
(see oracle_url variable, crate::oracle::fetch_signed_context call, and
candidate.signed_context assignment); change the log to avoid emitting sensitive
URL components by parsing the URL (e.g., with url::Url::parse) and logging only
non-sensitive parts such as scheme and host (and optionally path) or a
masked/hashed representation, then call tracing::warn! with a message like
"Failed to fetch oracle data" plus the sanitized host/path or mask and the error
(but not the full URL); update the tracing::warn! line accordingly and ensure
any helper masking/parsing logic is unit-tested if added.

}
}

let zero = Float::zero()?;

if candidate.ratio.gt(price_cap)? {
Expand Down
Loading
Loading