Skip to content

feat: signed context oracle fetch module (Phase 3)#2458

Open
hardyjosh wants to merge 14 commits intofeat/signed-context-oracle-discoveryfrom
feat/signed-context-oracle-fetch
Open

feat: signed context oracle fetch module (Phase 3)#2458
hardyjosh wants to merge 14 commits intofeat/signed-context-oracle-discoveryfrom
feat/signed-context-oracle-fetch

Conversation

@hardyjosh
Copy link
Contributor

@hardyjosh hardyjosh commented Feb 15, 2026

Caution

Merge order: Merge #2457 first, then this PR.

Motivation

Orders with SignedContextOracleV1 metadata now expose an oracle_url() (via #2457), but the SDK still hardcodes signedContext to vec![] in all take-order flows. Takers need the SDK to automatically fetch and pass signed context.

Solution

  • New oracle.rs module: fetch_signed_context(url) — HTTP GET with timeout, returns SignedContextV1
  • TakeOrderCandidate now carries signed_context: Vec<SignedContextV1>
  • Batch flow (build_take_order_candidates_for_pair): fetches oracle data concurrently alongside quotes
  • Single take flow (execute_single_take): accepts oracle_url: Option<String>, fetches before simulation
  • Config builder (build_take_orders_config_from_simulation): passes signed_context through to TakeOrderConfigV4

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 15, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds an oracle module and threads oracle-signed context support through metadata parsing, quote generation, candidate construction, and take execution. New async fetch functions retrieve SignedContextV1 from URLs; signed context is propagated into quotes, candidates, take calldata, UI, and tests.

Changes

Cohort / File(s) Summary
Core Oracle Module
crates/common/src/lib.rs, crates/common/src/oracle.rs
Adds public oracle module with OracleError, OracleResponse, conversion to SignedContextV1, fetch_signed_context and fetch_signed_contexts (async, concurrent), and tests.
Metadata Parsing
crates/common/src/parsed_meta.rs
Adds ParsedMeta::SignedContextOracleV1 variant and maps KnownMagic::SignedContextOracleV1 in from_meta_item; tests extended for parsing and round-tripping.
Quote Generation
crates/quote/src/order_quotes.rs, crates/common/src/raindex_client/order_quotes.rs
Adds get_order_quotes_with_context(...) accepting signed_context: Vec<SignedContextV1>; existing get_order_quotes delegates to it. Raindex client optionally fetches oracle-signed context and calls the context-aware API.
Order Oracle URL Accessor
crates/common/src/raindex_client/orders.rs
Adds oracle_url() (wasm and non-wasm) to RaindexOrder and passes oracle URL into take-calldata / execute_single_take callsites.
Candidate Building & Execution
crates/common/src/take_orders/candidates.rs, crates/common/src/raindex_client/take_orders/single.rs, crates/common/src/take_orders/config.rs
Adds signed_context: Vec<SignedContextV1> to TakeOrderCandidate; introduces fetch_oracle_for_order() and propagates signed_context through build_candidates_for_order, try_build_candidate, and execute_single_take(…, oracle_url: Option<String>). TakeOrderConfigV4 now uses candidate's signed context.
Test Updates & Helpers
crates/common/src/raindex_client/take_orders/single_tests.rs, crates/common/src/test_helpers.rs
Tests updated to include new oracle_url argument (often None); make_candidate() initializes signed_context as empty vector; test callsites adjusted.
UI
packages/ui-components/src/lib/components/detail/OrderDetail.svelte, packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte
Adds optional Oracle display: OrderDetail shows Oracle URL when present; TanstackOrderQuote displays an Oracle badge/tooltip when order.oracleUrl exists.
Other
lib/rain.interpreter
Submodule pointer bumped.

Sequence Diagram

sequenceDiagram
    participant Client as Requester
    participant Raindex as RaindexClient
    participant Oracle as Oracle Module
    participant Candidates as Candidate Builder
    participant Quote as Quote Generator

    Client->>Raindex: request quotes/orders
    Raindex->>Raindex: extract oracle_url from order meta
    alt oracle_url present
        Raindex->>Oracle: fetch_signed_context(url)
        Oracle-->>Raindex: SignedContextV1 or Error
    else no oracle_url or fetch fails
        Raindex-->>Raindex: use empty signed_context
    end
    Raindex->>Candidates: build_candidates_for_order(orders, signed_context)
    Candidates->>Oracle: fetch_oracle_for_order(...) (concurrent)
    Oracle-->>Candidates: Vec<SignedContextV1>
    Candidates-->>Raindex: Vec<TakeOrderCandidate> (includes signed_context)
    Raindex->>Quote: get_order_quotes_with_context(..., signed_context)
    Quote-->>Raindex: BatchOrderQuotesResponse
    Raindex-->>Client: returned quotes
Loading

Estimated Code Review Effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly Related PRs

Suggested Reviewers

  • 0xgleb
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main feature added in this PR: implementing an oracle fetch module for signed context data with Phase 3 designation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/signed-context-oracle-fetch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/common/src/raindex_client/take_orders/single_tests.rs (1)

134-145: 🧹 Nitpick | 🔵 Trivial

No tests exercise execute_single_take with an actual oracle_url.

All test call sites pass None for the new oracle_url parameter. Consider adding at least one test with a mocked oracle endpoint (using httpmock, which is already a dependency here) to verify that execute_single_take correctly fetches and threads signed context through to the take-order calldata.

🤖 Fix all issues with AI agents
In `@crates/common/src/lib.rs`:
- Line 10: Add a cfg guard to the oracle module declaration so it is excluded
from WASM builds: wrap the pub mod oracle; line with #[cfg(not(target_family =
"wasm"))] (matching how replays is guarded) because oracle.rs uses
reqwest::Client::builder().timeout(...) which fails when reqwest lacks the wasm
feature; ensure the oracle module is only compiled on non-wasm targets to avoid
compilation errors from raindex_client functions that are exposed to WASM.

In `@crates/common/src/oracle.rs`:
- Around line 44-73: Add URL validation in fetch_signed_context (and ensure
callers like fetch_signed_contexts benefit) by parsing the input with Url::parse
and rejecting non-HTTPS schemes (return OracleError::InvalidUrl). After parsing,
resolve the host to IPs (e.g., via lookup or DNS resolution) and reject
loopback/localhost and private IP ranges (RFC1918/unique local IPv6) before
constructing the reqwest Client and issuing the request; return
OracleError::InvalidUrl on validation failures. Keep the existing behavior of
returning other OracleError variants for network/json failures, and if you
intend to trust oracle URLs, instead document that assumption in the function
comment.

In `@crates/common/src/parsed_meta.rs`:
- Around line 210-265: Tests for oracle parsing
(test_from_meta_item_signed_context_oracle_v1, test_parse_multiple_with_oracle,
test_parse_from_bytes_with_oracle) should use insta snapshots instead of/manual
assertions; update each test to produce a serializable representation (e.g., the
ParsedMeta result or the SignedContextOracleV1 via
SignedContextOracleV1::to_meta_item / ParsedMeta::from_meta_item /
ParsedMeta::parse_multiple / ParsedMeta::parse_from_bytes) and call
insta::assert_yaml_snapshot! (or assert_debug_snapshot!) to record the output,
replacing or augmenting the current assert_eq! / matches! checks so future
metadata changes are captured by snapshots. Ensure the tests import insta and
use stable snapshot names tied to the test function names.

In `@crates/common/src/take_orders/candidates.rs`:
- Around line 94-122: The comment claims per-order work is concurrent but the
code awaits order.get_quotes before calling fetch_oracle_for_order, making them
sequential; fix by starting both futures before awaiting (e.g., create
quotes_fut = order.get_quotes(block_number, gas_string) and signed_context_fut =
fetch_oracle_for_order(order) then await both together with futures::join! or
join to produce (quotes, signed_context)), referencing the futures creation in
the map/async block where order.get_quotes and fetch_oracle_for_order are
called; alternatively, if you prefer the minimal change, update the comment to
remove "concurrently" so it accurately reflects the current sequential behavior.

In `@crates/quote/src/order_quotes.rs`:
- Around line 50-58: The function get_order_quotes_with_context accepts a
Vec<SignedContextV1> but in practice callers only pass a single per-order
context and the API is misleading; change the signature to accept an
Option<SignedContextV1> (or a single SignedContextV1) to make it explicit that
one signed context is applied to all orders, update the doc comment to state
"signed_context is a single signed context applied to all quote targets for all
orders", and adjust any call sites (e.g., where callers fetch per-order context)
to pass the single context or None accordingly; keep SgOrder and SignedContextV1
types unchanged.

pub mod fuzz;
pub mod local_db;
pub mod meta;
pub mod oracle;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the oracle module
fd -t f oracle.rs crates/common/src

Repository: rainlanguage/rain.orderbook

Length of output: 98


🏁 Script executed:

# Check the main Cargo.toml for reqwest configuration
cat crates/common/Cargo.toml

Repository: rainlanguage/rain.orderbook

Length of output: 2576


🏁 Script executed:

# Look for WASM-related build configurations
rg "wasm" --include="*.toml" --include="*.yml" --include="*.yaml" | head -50

Repository: rainlanguage/rain.orderbook

Length of output: 512


🏁 Script executed:

# Read the oracle.rs file to see what it uses
cat -n crates/common/src/oracle.rs

Repository: rainlanguage/rain.orderbook

Length of output: 4308


🏁 Script executed:

# Check workspace Cargo.toml for reqwest configuration
fd -t f Cargo.toml | head -5 | xargs grep -l "reqwest" | head -1 | xargs cat | grep -A 10 -B 2 "reqwest"

Repository: rainlanguage/rain.orderbook

Length of output: 427


🏁 Script executed:

# Search for WASM builds in CI/github workflows
fd "\.yml\|\.yaml" .github/workflows --exec grep -l "wasm" {} \; 2>/dev/null || echo "No wasm in workflows"

Repository: rainlanguage/rain.orderbook

Length of output: 53


🏁 Script executed:

# Check the full lib.rs to see what's exported and how oracle fits
cat -n crates/common/src/lib.rs

Repository: rainlanguage/rain.orderbook

Length of output: 972


🏁 Script executed:

# Search for uses of oracle module
rg "oracle" crates/common/src --type rs -n

Repository: rainlanguage/rain.orderbook

Length of output: 97


🏁 Script executed:

# Check if there are any WASM-specific build targets or configurations
rg "cdylib\|wasm" Cargo.toml crates/*/Cargo.toml -A 2 -B 2

Repository: rainlanguage/rain.orderbook

Length of output: 53


🏁 Script executed:

# Search oracle usage with correct syntax
rg "oracle" crates/common/src -n

Repository: rainlanguage/rain.orderbook

Length of output: 5667


🏁 Script executed:

# Check raindex_client to see if other non-guarded modules use wasm-incompatible features
head -50 crates/common/src/raindex_client/mod.rs

Repository: rainlanguage/rain.orderbook

Length of output: 1634


🏁 Script executed:

# Search for actual WASM compilation or build targets
fd Cargo.toml | xargs grep -l "wasm32"

Repository: rainlanguage/rain.orderbook

Length of output: 55


🏁 Script executed:

# Check what features are actually used when building
rg "wasm_bindgen" crates/common/src --type-list

Repository: rainlanguage/rain.orderbook

Length of output: 460


🏁 Script executed:

# Check if the functions that call oracle are exposed/used by WASM
rg "fetch_oracle_for_order|fetch_signed_context" crates/common/src/take_orders/candidates.rs -B 5 -A 5

Repository: rainlanguage/rain.orderbook

Length of output: 1272


🏁 Script executed:

# Check if these WASM-exposed raindex_client functions actually call oracle code
rg "pub async fn|pub fn" crates/common/src/raindex_client/order_quotes.rs | head -20

Repository: rainlanguage/rain.orderbook

Length of output: 242


🏁 Script executed:

# See if there's any cfg guard around the actual reqwest usage in oracle
rg "Client::builder|timeout" crates/common/src/oracle.rs -B 5 -A 5

Repository: rainlanguage/rain.orderbook

Length of output: 526


🏁 Script executed:

# Check if take_orders is part of WASM-exposed APIs
rg "take_orders" crates/common/src/raindex_client/mod.rs

Repository: rainlanguage/rain.orderbook

Length of output: 91


🏁 Script executed:

# Check if there are any WASM build tests or targets
rg "wasm32\|wasm_bindgen_test" Cargo.toml .github/ -l

Repository: rainlanguage/rain.orderbook

Length of output: 53


🏁 Script executed:

# Check the actual build configuration - does cdylib mean WASM or something else?
rg "crate-type" crates/*/Cargo.toml -B 2 -A 2

Repository: rainlanguage/rain.orderbook

Length of output: 593


Add cfg guard to oracle module for WASM compatibility.

The oracle module uses reqwest::Client::builder().timeout(...) (line 49–50 of oracle.rs), which is unavailable on WASM targets when reqwest is not built with the wasm feature. Since the workspace reqwest has only the json feature enabled, this code cannot compile for WASM. The module is publicly exported without guards and is called from WASM-exposed raindex_client functions. Add #[cfg(not(target_family = "wasm"))] to the oracle module declaration (line 10) to prevent this compilation failure, consistent with the pattern used for replays (line 15–16).

🤖 Prompt for AI Agents
In `@crates/common/src/lib.rs` at line 10, Add a cfg guard to the oracle module
declaration so it is excluded from WASM builds: wrap the pub mod oracle; line
with #[cfg(not(target_family = "wasm"))] (matching how replays is guarded)
because oracle.rs uses reqwest::Client::builder().timeout(...) which fails when
reqwest lacks the wasm feature; ensure the oracle module is only compiled on
non-wasm targets to avoid compilation errors from raindex_client functions that
are exposed to WASM.

Comment on lines +44 to +73
/// Fetch signed context from an oracle endpoint.
///
/// The endpoint must respond to a GET request with a JSON body matching
/// `OracleResponse` (signer, context, signature).
pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> {
let client = Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()?;

let response: OracleResponse = client
.get(url)
.send()
.await?
.error_for_status()?
.json()
.await?;

Ok(response.into())
}

/// Fetch signed contexts for multiple oracle URLs concurrently.
///
/// Returns a vec of results - one per URL. Failed fetches return errors
/// rather than failing the entire batch, so callers can decide how to handle
/// partial failures.
pub async fn fetch_signed_contexts(urls: &[String]) -> Vec<Result<SignedContextV1, OracleError>> {
let futures: Vec<_> = urls.iter().map(|url| fetch_signed_context(url)).collect();

futures::future::join_all(futures).await
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find where fetch_signed_context and fetch_signed_contexts are called
rg "fetch_signed_context" --type rust -A 3 -B 3

Repository: rainlanguage/rain.orderbook

Length of output: 4051


🏁 Script executed:

# Look for oracle_url usage and how it's obtained
rg "oracle_url" --type rust -A 3 -B 3

Repository: rainlanguage/rain.orderbook

Length of output: 4737


🏁 Script executed:

# Check OracleError definition to understand current error handling
rg "OracleError" --type rust -A 5 | head -50

Repository: rainlanguage/rain.orderbook

Length of output: 1240


🏁 Script executed:

# Search for order metadata or similar patterns related to oracle URLs
rg "order.*metadata|metadata.*oracle" --type rust -A 3 -B 3

Repository: rainlanguage/rain.orderbook

Length of output: 6648


🏁 Script executed:

# Check the full oracle.rs file to understand context
cat -n crates/common/src/oracle.rs

Repository: rainlanguage/rain.orderbook

Length of output: 4308


🏁 Script executed:

# Search for oracle endpoint documentation or trust assumptions
rg "oracle.*endpoint|oracle.*trusted|oracle.*url.*valid" --type rust -i -B 3 -A 3

Repository: rainlanguage/rain.orderbook

Length of output: 2052


🏁 Script executed:

# Look for URL validation patterns elsewhere in the codebase
rg "Url::parse|url.*parse|https.*check|localhost.*block" --type rust -B 2 -A 2

Repository: rainlanguage/rain.orderbook

Length of output: 50384


🏁 Script executed:

# Check if there's any configuration or allowlist for oracle URLs
rg "oracle.*config|allowlist|whitelist" --type rust -i -B 2 -A 2

Repository: rainlanguage/rain.orderbook

Length of output: 53


🏁 Script executed:

# Look at the ParsedMeta to understand oracle URL origin better
rg "SignedContextOracleV1|ParsedMeta" --type rust -B 3 -A 3 | head -60

Repository: rainlanguage/rain.orderbook

Length of output: 4527


Add URL validation to prevent SSRF attacks via oracle endpoints.

Oracle URLs originate from user-submitted order metadata (via ParsedMeta::SignedContextOracleV1) and are fetched without validation. This creates an SSRF vector—an attacker could supply URLs targeting internal services, localhost, or private IP ranges.

Enforce HTTPS, block localhost/loopback addresses, and consider blocking private IP ranges before issuing the HTTP request. The OracleError::InvalidUrl variant already exists to support this validation. If oracle URLs are guaranteed trusted, document that assumption in the function comments.

🛡️ Suggested guardrail
+use std::net::IpAddr;
+use url::Url;
+
 pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> {
+    let parsed = Url::parse(url).map_err(|e| OracleError::InvalidUrl(e.to_string()))?;
+    if parsed.scheme() != "https" {
+        return Err(OracleError::InvalidUrl("oracle URL must use https".to_string()));
+    }
+    let host = parsed
+        .host_str()
+        .ok_or_else(|| OracleError::InvalidUrl("oracle URL missing host".to_string()))?;
+    if host.eq_ignore_ascii_case("localhost") || host.ends_with(".localhost") {
+        return Err(OracleError::InvalidUrl("oracle URL must not target localhost".to_string()));
+    }
+    if let Ok(ip) = host.parse::<IpAddr>() {
+        if ip.is_loopback() {
+            return Err(OracleError::InvalidUrl("oracle URL must not target loopback".to_string()));
+        }
+    }
+
     let client = Client::builder()
         .timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
         .build()?;
📝 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
/// Fetch signed context from an oracle endpoint.
///
/// The endpoint must respond to a GET request with a JSON body matching
/// `OracleResponse` (signer, context, signature).
pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> {
let client = Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()?;
let response: OracleResponse = client
.get(url)
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(response.into())
}
/// Fetch signed contexts for multiple oracle URLs concurrently.
///
/// Returns a vec of results - one per URL. Failed fetches return errors
/// rather than failing the entire batch, so callers can decide how to handle
/// partial failures.
pub async fn fetch_signed_contexts(urls: &[String]) -> Vec<Result<SignedContextV1, OracleError>> {
let futures: Vec<_> = urls.iter().map(|url| fetch_signed_context(url)).collect();
futures::future::join_all(futures).await
}
use std::net::IpAddr;
use url::Url;
/// Fetch signed context from an oracle endpoint.
///
/// The endpoint must respond to a GET request with a JSON body matching
/// `OracleResponse` (signer, context, signature).
pub async fn fetch_signed_context(url: &str) -> Result<SignedContextV1, OracleError> {
let parsed = Url::parse(url).map_err(|e| OracleError::InvalidUrl(e.to_string()))?;
if parsed.scheme() != "https" {
return Err(OracleError::InvalidUrl("oracle URL must use https".to_string()));
}
let host = parsed
.host_str()
.ok_or_else(|| OracleError::InvalidUrl("oracle URL missing host".to_string()))?;
if host.eq_ignore_ascii_case("localhost") || host.ends_with(".localhost") {
return Err(OracleError::InvalidUrl("oracle URL must not target localhost".to_string()));
}
if let Ok(ip) = host.parse::<IpAddr>() {
if ip.is_loopback() {
return Err(OracleError::InvalidUrl("oracle URL must not target loopback".to_string()));
}
}
let client = Client::builder()
.timeout(Duration::from_secs(DEFAULT_TIMEOUT_SECS))
.build()?;
let response: OracleResponse = client
.get(url)
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(response.into())
}
/// Fetch signed contexts for multiple oracle URLs concurrently.
///
/// Returns a vec of results - one per URL. Failed fetches return errors
/// rather than failing the entire batch, so callers can decide how to handle
/// partial failures.
pub async fn fetch_signed_contexts(urls: &[String]) -> Vec<Result<SignedContextV1, OracleError>> {
let futures: Vec<_> = urls.iter().map(|url| fetch_signed_context(url)).collect();
futures::future::join_all(futures).await
}
🤖 Prompt for AI Agents
In `@crates/common/src/oracle.rs` around lines 44 - 73, Add URL validation in
fetch_signed_context (and ensure callers like fetch_signed_contexts benefit) by
parsing the input with Url::parse and rejecting non-HTTPS schemes (return
OracleError::InvalidUrl). After parsing, resolve the host to IPs (e.g., via
lookup or DNS resolution) and reject loopback/localhost and private IP ranges
(RFC1918/unique local IPv6) before constructing the reqwest Client and issuing
the request; return OracleError::InvalidUrl on validation failures. Keep the
existing behavior of returning other OracleError variants for network/json
failures, and if you intend to trust oracle URLs, instead document that
assumption in the function comment.

Comment on lines +210 to +265
#[test]
fn test_from_meta_item_signed_context_oracle_v1() {
let oracle = SignedContextOracleV1::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::SignedContextOracleV1(parsed_oracle) => {
assert_eq!(parsed_oracle.url(), "https://oracle.example.com/prices/eth-usd");
}
_ => panic!("Expected SignedContextOracleV1"),
}
}

#[test]
fn test_parse_multiple_with_oracle() {
let source = get_default_dotrain_source();
let oracle = SignedContextOracleV1::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::SignedContextOracleV1(parsed_oracle) => {
assert_eq!(parsed_oracle.url(), "https://oracle.example.com/feed");
}
_ => panic!("Expected SignedContextOracleV1"),
}
}

#[test]
fn test_parse_from_bytes_with_oracle() {
let oracle =
SignedContextOracleV1::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::SignedContextOracleV1(o) if o.url() == "https://oracle.example.com/prices/eth-usd")
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider insta snapshots for the new oracle parsing tests.
Assertions are fine, but snapshots can make future metadata changes easier to review. As per coding guidelines, "Prefer insta snapshots for Rust testing".

🤖 Prompt for AI Agents
In `@crates/common/src/parsed_meta.rs` around lines 210 - 265, Tests for oracle
parsing (test_from_meta_item_signed_context_oracle_v1,
test_parse_multiple_with_oracle, test_parse_from_bytes_with_oracle) should use
insta snapshots instead of/manual assertions; update each test to produce a
serializable representation (e.g., the ParsedMeta result or the
SignedContextOracleV1 via SignedContextOracleV1::to_meta_item /
ParsedMeta::from_meta_item / ParsedMeta::parse_multiple /
ParsedMeta::parse_from_bytes) and call insta::assert_yaml_snapshot! (or
assert_debug_snapshot!) to record the output, replacing or augmenting the
current assert_eq! / matches! checks so future metadata changes are captured by
snapshots. Ensure the tests import insta and use stable snapshot names tied to
the test function names.

Comment on lines +94 to +122
type QuoteWithContext = (
Result<Vec<RaindexOrderQuote>, RaindexError>,
Vec<SignedContextV1>,
);

// Fetch quotes and oracle data concurrently for each order
let results: Vec<QuoteWithContext> = futures::stream::iter(orders.iter().map(|order| {
let gas_string = gas_string.clone();
async move {
let quotes = order.get_quotes(block_number, gas_string).await;
let signed_context = fetch_oracle_for_order(order).await;
(quotes, signed_context)
}
}))
.buffered(DEFAULT_QUOTE_CONCURRENCY)
.collect()
.await;

orders
.iter()
.zip(quote_results)
.map(|(order, quotes_result)| {
build_candidates_for_order(order, quotes_result?, input_token, output_token)
.zip(results)
.map(|(order, (quotes_result, signed_context))| {
build_candidates_for_order(
order,
quotes_result?,
input_token,
output_token,
signed_context,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Comment says “concurrent” but per‑order fetch is sequential.
Inside each task, quotes are awaited before oracle fetch. Either update the comment or run both futures concurrently.

✏️ Minimal fix (comment only)
-    // Fetch quotes and oracle data concurrently for each order
+    // Fetch quotes and oracle data per order (parallelized across orders)
🤖 Prompt for AI Agents
In `@crates/common/src/take_orders/candidates.rs` around lines 94 - 122, The
comment claims per-order work is concurrent but the code awaits order.get_quotes
before calling fetch_oracle_for_order, making them sequential; fix by starting
both futures before awaiting (e.g., create quotes_fut =
order.get_quotes(block_number, gas_string) and signed_context_fut =
fetch_oracle_for_order(order) then await both together with futures::join! or
join to produce (quotes, signed_context)), referencing the futures creation in
the map/async block where order.get_quotes and fetch_oracle_for_order are
called; alternatively, if you prefer the minimal change, update the comment to
remove "concurrently" so it accurately reflects the current sequential behavior.

Comment on lines 50 to 58
/// Get order quotes with optional signed context data.
/// The signed_context is applied to all quote targets for all orders.
pub async fn get_order_quotes_with_context(
orders: Vec<SgOrder>,
block_number: Option<u64>,
rpcs: Vec<String>,
gas: Option<u64>,
signed_context: Vec<SignedContextV1>,
) -> Result<Vec<BatchOrderQuotesResponse>, Error> {
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the function definition and all call sites
rg -n "get_order_quotes_with_context" crates/quote/src/

Repository: rainlanguage/rain.orderbook

Length of output: 267


🏁 Script executed:

#!/bin/bash
# Look at the function implementation and surrounding context
cat -n crates/quote/src/order_quotes.rs | head -100

Repository: rainlanguage/rain.orderbook

Length of output: 4518


🏁 Script executed:

#!/bin/bash
# Search for external callers across the codebase
rg -n "get_order_quotes_with_context" --type rs -C 2

Repository: rainlanguage/rain.orderbook

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Get the complete order_quotes.rs file to see how signed_context is used
wc -l crates/quote/src/order_quotes.rs

Repository: rainlanguage/rain.orderbook

Length of output: 107


🏁 Script executed:

#!/bin/bash
# Search for external call sites across the codebase
rg "get_order_quotes_with_context" -C 3 --max-count 20

Repository: rainlanguage/rain.orderbook

Length of output: 2099


🏁 Script executed:

#!/bin/bash
# Read the complete call site in crates/common/src/raindex_client/order_quotes.rs around the function call
cat -n crates/common/src/raindex_client/order_quotes.rs | sed -n '1,200p'

Repository: rainlanguage/rain.orderbook

Length of output: 9717


🏁 Script executed:

#!/bin/bash
# Search more broadly for any multi-order batching patterns
rg "get_order_quotes" -C 5 | head -100

Repository: rainlanguage/rain.orderbook

Length of output: 7001


🏁 Script executed:

#!/bin/bash
# Read the implementation of get_order_quotes_with_context to see how signed_context is used
cat -n crates/quote/src/order_quotes.rs | sed -n '50,250p'

Repository: rainlanguage/rain.orderbook

Length of output: 8573


🏁 Script executed:

#!/bin/bash
# Search for uses of signed_context variable within the file
rg "signed_context" crates/quote/src/order_quotes.rs -n -C 2

Repository: rainlanguage/rain.orderbook

Length of output: 761


🏁 Script executed:

#!/bin/bash
# Check if there's any documentation or comments about the intended use case
rg -n "signed_context\|SignedContextV1" crates/quote/src/order_quotes.rs -B 3 -A 3 | head -60

Repository: rainlanguage/rain.orderbook

Length of output: 53


🏁 Script executed:

#!/bin/bash
# Check if there are any tests that use signed_context
rg -n "signed_context" crates/quote/src/order_quotes.rs | tail -20

Repository: rainlanguage/rain.orderbook

Length of output: 256


Confirmation: Current batched callers only batch single orders and do not require per-order signed context.

All existing callers (e.g., crates/common/src/raindex_client/order_quotes.rs:138–144) batch single orders and fetch the signed context per-order from the oracle, then pass it to the function. The API design—which allows multiple orders but applies a single context to all—is not currently violated in practice. However, the function signature is misleading for future maintainers: the parameter signed_context: Vec<SignedContextV1> should either be documented more explicitly or the API redesigned to prevent accidental misuse if multi-order batching with different oracle contexts becomes required.

🤖 Prompt for AI Agents
In `@crates/quote/src/order_quotes.rs` around lines 50 - 58, The function
get_order_quotes_with_context accepts a Vec<SignedContextV1> but in practice
callers only pass a single per-order context and the API is misleading; change
the signature to accept an Option<SignedContextV1> (or a single SignedContextV1)
to make it explicit that one signed context is applied to all orders, update the
doc comment to state "signed_context is a single signed context applied to all
quote targets for all orders", and adjust any call sites (e.g., where callers
fetch per-order context) to pass the single context or None accordingly; keep
SgOrder and SignedContextV1 types unchanged.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@crates/common/src/oracle.rs`:
- Around line 15-16: OracleError::InvalidUrl is dead code; either remove the
variant from the OracleError enum or add URL validation where external URLs are
accepted (e.g., the Oracle constructor/factory or any function that accepts
endpoint strings) and return OracleError::InvalidUrl with the offending String
when validation fails; update callers to handle the new error if you implement
validation.

Comment on lines +15 to +16
#[error("Invalid URL: {0}")]
InvalidUrl(String),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg "OracleError::InvalidUrl|InvalidUrl\(" --type rust -n

Repository: rainlanguage/rain.orderbook

Length of output: 125


Remove unused InvalidUrl variant or implement URL validation.

OracleError::InvalidUrl is defined but never constructed anywhere in the codebase. Either remove this dead-code variant or implement URL validation logic that produces it.

🤖 Prompt for AI Agents
In `@crates/common/src/oracle.rs` around lines 15 - 16, OracleError::InvalidUrl is
dead code; either remove the variant from the OracleError enum or add URL
validation where external URLs are accepted (e.g., the Oracle
constructor/factory or any function that accepts endpoint strings) and return
OracleError::InvalidUrl with the offending String when validation fails; update
callers to handle the new error if you implement validation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/ui-components/src/lib/components/detail/TanstackOrderQuote.svelte (1)

11-26: 🧹 Nitpick | 🔵 Trivial

Merge Badge into the existing flowbite-svelte import.

Badge (line 26) is imported separately from flowbite-svelte, but there's already an import block from the same package on lines 11-17. Consolidating avoids a duplicate import source.

♻️ Suggested merge
 import {
 	Table,
 	TableBody,
 	TableBodyCell,
 	TableBodyRow,
 	TableHead,
-	TableHeadCell
+	TableHeadCell,
+	Badge
 } from 'flowbite-svelte';
 ...
-import { Badge } from 'flowbite-svelte';
🤖 Fix all issues with AI agents
In `@packages/ui-components/src/lib/components/detail/OrderDetail.svelte`:
- Around line 203-225: The anchor rendering for data.oracleUrl in
OrderDetail.svelte can be abused with javascript: URIs; validate and sanitize
the URL before using it in the href of the <a> inside CardProperty: ensure the
value parsed from data.oracleUrl has an allowed scheme (http or https) using a
URL parser, and if it is missing/invalid or not http(s) render it as plain text
(or a disabled/non-clickable element) instead of setting href, preserving
current target/rel attributes for valid URLs only; update the logic around
data.oracleUrl to perform this check and fallback display so malicious
javascript: URIs cannot be injected.

@hardyjosh hardyjosh changed the base branch from main to feat/signed-context-oracle-discovery February 17, 2026 12:48
@hardyjosh hardyjosh force-pushed the feat/signed-context-oracle-fetch branch from 929dcff to 1d940d3 Compare February 17, 2026 16:35
Josh Hardy added 10 commits February 22, 2026 22:55
… flows

Phase 3 of signed context oracle discovery:

- New oracle.rs module with fetch_signed_context(url) and OracleResponse type
- OracleResponse maps directly to SignedContextV1 (signer, context as bytes32[], signature)
- Added signed_context field to TakeOrderCandidate
- Wired oracle fetching into:
  - build_take_order_candidates_for_pair (batch flow, concurrent fetch)
  - execute_single_take (single take flow, oracle_url param)
  - build_take_orders_config_from_simulation (passes through to TakeOrderConfigV4)
- Oracle fetch is best-effort: failures log a warning and use empty signed context
- 3 oracle tests + 9 parsed_meta tests passing
- Add get_order_quotes_with_context() to quote crate (accepts signed_context param)
- RaindexOrder.get_quotes() now fetches oracle data and passes to quotes
- Original get_order_quotes() unchanged (delegates with empty context)
reqwest::ClientBuilder::timeout() is not available on WASM targets.
Use cfg(not(target_family = "wasm")) to only set it on native.
- OrderDetail: show Oracle card property with URL link when order has oracle metadata
  - Includes tooltip explaining signed context oracle usage
- TanstackOrderQuote: show purple 'Oracle' badge next to quotes heading when oracle is active
  - Indicates quotes include signed context data from oracle
- Both use the oracleUrl getter exposed via WASM bindings on RaindexOrder
The oracle endpoint now receives order details via POST so it can tailor
responses based on the specific order, counterparty, and IO indexes.

POST body: abi.encode(OrderV4, inputIOIndex, outputIOIndex, counterparty)

Falls back to GET when no body is provided (simple oracles).
Callers currently pass None - ABI encoding will be wired in once the
order data is available at each call site.
POST with ABI-encoded order data is mandatory. Callers currently pass
empty vec — will be wired to abi.encode(OrderV4, inputIOIndex,
outputIOIndex, counterparty) at each call site.
- encode_oracle_body: abi.encode(OrderV4, inputIOIndex, outputIOIndex, counterparty)
- get_quotes: fetches oracle per IO pair concurrently, counterparty=address(0)
- build_take_order_candidates: fetches oracle per quote pair
- execute_single_take: encodes with actual taker as counterparty
- get_order_quotes_with_context_fn: accepts per-pair context callback
- Oracle fetch logic moved from common to quote crate (common re-exports)
- get_order_quotes now extracts oracle URL directly from SgOrder.meta
- Removed get_order_quotes_with_context and get_order_quotes_with_context_fn
- No more closures, HashMaps, or pre-fetching — oracle context fetched
  inline per IO pair inside the quote loop
- RaindexOrder.get_quotes() simplified to just call get_order_quotes()
@hardyjosh hardyjosh force-pushed the feat/signed-context-oracle-fetch branch from 1d940d3 to 63ea51f Compare February 22, 2026 22:55
@hardyjosh hardyjosh self-assigned this Feb 23, 2026
Josh Hardy added 3 commits February 23, 2026 20:26
- Add encode_oracle_body_batch() for array encoding: abi.encode((OrderV4, uint256, uint256, address)[])
- Update fetch_signed_context_batch() to handle Vec responses
- Maintain backward compatibility with single request functions
- Add comprehensive tests for both single and batch formats
- Response format now expects JSON array per spec
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants