Skip to content

feat: oracle signed context - full stack (Phases 2-7)#2505

Merged
hardyjosh merged 49 commits intomainfrom
feat/oracle-url-yaml-parsing
Mar 24, 2026
Merged

feat: oracle signed context - full stack (Phases 2-7)#2505
hardyjosh merged 49 commits intomainfrom
feat/oracle-url-yaml-parsing

Conversation

@hardyjosh
Copy link
Contributor

@hardyjosh hardyjosh commented Mar 11, 2026

Merges the full reviewed oracle signed context stack into main.

This branch accumulates all the reviewed+approved stacked PRs:

All code was previously reviewed and approved in the stacked PRs.

Summary by CodeRabbit

  • New Features

    • Orders may include optional oracle URLs; oracle-signed context is fetched per-pair and included in quotes and order processing.
  • Configuration

    • Add optional oracle-url parameter per order to specify an oracle endpoint (YAML/key supported).
  • User Interface

    • Order details show an Oracle field with a clickable link when configured.
  • Behavior

    • Oracle data is fetched best-effort (single or batch); failures are logged and processing continues.

Josh Hardy added 24 commits February 13, 2026 18:22
…n RaindexOrder

- Add SignedContextOracleV1 variant to ParsedMeta enum
- Add match arm for KnownMagic::SignedContextOracleV1 in parsing logic
- Add oracle_url() wasm_bindgen getter on RaindexOrder
- Depends on rain.metadata feat/signed-context-oracle-meta branch
- Add 3 tests for SignedContextOracleV1 parsing (from_meta_item,
  parse_multiple, parse_from_bytes roundtrip)
- Add oracle_url() to non-wasm impl block (mirrors wasm getter)
- All 11 parsed_meta tests passing
…leV1 metadata

Points rain.interpreter at feat/signed-context-oracle-meta-submodule branch
which updates the rain.metadata submodule to feat/signed-context-oracle-meta.
Matches rename in rain.metadata#92. The metadata type is specific to
the Raindex calculateOrderIO entrypoint.
… 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()
- 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
Adds optional oracle-url field to OrderCfg, parsed from the YAML front
matter orders section. When present, this URL identifies a signed context
oracle server for the order.

Changes:
- Add oracle_url: Option<String> to OrderCfg struct
- Parse oracle-url via optional_string in YAML parsing
- Add oracle-url to ALLOWED_ORDER_KEYS
- Update Default and PartialEq impls
- Add test for oracle-url parsing (present + absent)

Spec: rainlanguage/specs#45
Chained on: #2459 (Phase 4)
When an order has oracle_url set in its config, new_from_deployment now
creates a SignedContextOracleV1 meta item and includes it in the order's
additional_meta. This means orders deployed with oracle-url in their YAML
will have the oracle endpoint encoded in their onchain RainMetaDocumentV1,
making it discoverable by takers and indexers (Phase 2 reads it back).

Changes:
- Import SignedContextOracleV1 in add_order.rs
- In new_from_deployment: parse oracle_url, create meta item, append to
  additional_meta
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional per-order oracle_url and a new oracle module; fetches signed context from oracle endpoints and threads SignedContextV1 through quote generation, candidate construction, and take-order execution flows.

Changes

Cohort / File(s) Summary
Order configuration & parsing
crates/settings/src/order.rs, crates/settings/src/yaml/context.rs, crates/settings/src/gui.rs, crates/common/src/add_order.rs
Added oracle_url: Option<String> to OrderCfg, wired YAML key oracle-url, updated defaults, parsing, equality, tests, and test fixtures.
Oracle module (quote crate)
crates/quote/src/oracle.rs, crates/quote/src/lib.rs, crates/quote/Cargo.toml
New oracle module: request/response types, encoders (encode_oracle_body*), fetch functions (fetch_signed_context[_batch]), extract_oracle_url, error types, conversions, and unit tests; added rain-metadata and futures deps.
Quote pipeline integration
crates/quote/src/order_quotes.rs
New get_order_quotes exposes per-order/per-pair oracle extraction, best-effort signed-context fetches, and propagates signedContext into batch quote responses (adds chunk_size param).
Common crate oracle re-export
crates/common/src/oracle.rs, crates/common/src/lib.rs
Added common::oracle module that re-exports rain_orderbook_quote::oracle::* for cross-crate usage.
Take-orders candidates & flow
crates/common/src/take_orders/candidates.rs, crates/common/src/take_orders/config.rs, crates/common/src/test_helpers.rs
Added signed_context: Vec<SignedContextV1> to TakeOrderCandidate; updated try_build_candidate to accept it; added fetch_oracle_for_pair and per-pair oracle fetch logic; propagate signed context into take-order config.
Take-orders execution & raindex client
crates/common/src/raindex_client/take_orders/single.rs, crates/common/src/raindex_client/orders.rs, crates/common/src/raindex_client/order_quotes.rs
execute_single_take gains oracle_url: Option<String> parameter; callers forward self.oracle_url(); single-take fetches signed context when provided; removed duplicate into_sg_order() conversion in get_quotes.
Tests & single-take tests
crates/common/src/raindex_client/take_orders/single_tests.rs
Updated tests to pass None for the new oracle_url parameter and adjusted expectations accordingly.
UI: Order detail
packages/ui-components/src/lib/components/detail/OrderDetail.svelte
Conditionally renders an Oracle section with a clickable data.oracleUrl when present.

Sequence Diagram

sequenceDiagram
    participant Client
    participant QuoteModule
    participant OracleModule
    participant OracleEndpoint
    participant TakeOrderModule

    Client->>QuoteModule: get_order_quotes(orders, block, rpcs, chunk_size)
    QuoteModule->>QuoteModule: extract_oracle_url(order)
    alt oracle_url present
        QuoteModule->>OracleModule: encode_oracle_body(order, in_idx, out_idx, counterparty)
        OracleModule-->>QuoteModule: body (bytes)
        QuoteModule->>OracleEndpoint: POST body (application/octet-stream)
        OracleEndpoint-->>QuoteModule: OracleResponse
        QuoteModule->>OracleModule: convert OracleResponse -> SignedContextV1
        OracleModule-->>QuoteModule: SignedContextV1
        QuoteModule->>QuoteModule: attach signed_context to quote
    else oracle_url absent
        QuoteModule->>QuoteModule: use empty signed_context
    end
    QuoteModule-->>TakeOrderModule: BatchOrderQuotesResponse (with signed_context)
    TakeOrderModule->>TakeOrderModule: build candidates including signed_context
    TakeOrderModule->>TakeOrderModule: execute_single_take(..., oracle_url)
    alt oracle_url provided
        TakeOrderModule->>OracleModule: fetch_signed_context(url, body)
        OracleModule-->>TakeOrderModule: SignedContextV1 (or error -> warn)
        TakeOrderModule->>TakeOrderModule: set candidate.signed_context
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • JuaniRios
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and comprehensively reflects the main objective: implementing oracle signed context across the full stack (Phases 2-7), which is the primary focus of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/oracle-url-yaml-parsing

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: 6

Caution

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

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

87-95: 🧹 Nitpick | 🔵 Trivial

Add one non-empty signedContext regression test here.

Line 94 is now the only point where fetched oracle data enters TakeOrderConfigV4. The current tests in this module still build candidates via make_simulation_candidate from crates/common/src/test_helpers.rs, which hardcodes signed_context: vec![], and the updated execute_single_take tests only pass None for oracle_url. A regression back to an empty signedContext would still leave this PR green; please assert a non-empty config.orders[*].signedContext somewhere in this path.

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

In `@crates/common/src/take_orders/config.rs` around lines 87 - 95, The current
tests never exercise non-empty signedContext because candidates are built with
make_simulation_candidate (which sets signed_context: vec![]) and existing
execute_single_take tests pass oracle_url = None; add or update a unit test that
constructs a simulation with at least one leg whose candidate.signed_context is
populated (or call the code path that fetches oracle data so signedContext is
set) and then after building TakeOrderConfigV4 via the map (the orders variable
produced from sim.legs.iter().map(...) that creates TakeOrderConfigV4) assert
that config.orders[i].signedContext is non-empty; reference the
TakeOrderConfigV4 construction and the execute_single_take test helper to locate
where to inject the non-empty signed context and the assertion.
crates/quote/src/order_quotes.rs (1)

53-60: ⚠️ Potential issue | 🟠 Major

Oracle-backed historical quotes ignore block_number.

Lines 53-60 pin the EVM quote to req_block_number, but Lines 97-106 always fetch live oracle context with no block/timestamp input. For oracle-driven orders, block_number: Some(...) now mixes historical chain state with current oracle state and can return a quote that never existed. Either reject historical quotes for oracle-backed orders, or make the oracle request block-aware.

Also applies to: 97-106

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

In `@crates/quote/src/order_quotes.rs` around lines 53 - 60, The code computes
req_block_number using
ReadableClient::new_from_http_urls(...).get_block_number() but later (around the
oracle context fetch block at lines ~97-106) always requests live oracle state,
causing mismatches for oracle-backed orders when block_number is Some(...); fix
by detecting oracle-backed orders and either rejecting historical quotes (return
an error if block_number.is_some() and the order is oracle-backed) or by making
the oracle request block-aware: extend the oracle-context fetch to accept a
block/timestamp parameter and pass req_block_number there so the oracle snapshot
and chain state are consistent (locate req_block_number, the
ReadableClient::new_from_http_urls/get_block_number call, and the oracle-context
fetch block to implement the check or the parameter plumbing).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/common/src/add_order.rs`:
- Around line 134-147: The block that appends a RaindexSignedContextOracleV1
meta item can create duplicates when additional_meta already contains one;
update the logic in the additional_meta construction (the code that reads
deployment.order.oracle_url, calls RaindexSignedContextOracleV1::parse(...) and
pushes oracle.to_meta_item()) to first scan existing meta for an existing
RaindexSignedContextOracleV1 entry (by matching meta item type/name or parsing
existing meta into RaindexSignedContextOracleV1) and only push the new oracle
meta if none exists or if you explicitly want to replace it; ensure you
reference the same symbols (additional_meta,
RaindexSignedContextOracleV1::parse, oracle.to_meta_item,
deployment.order.oracle_url) so the duplicate is prevented or handled
deterministically.

In `@crates/common/src/raindex_client/take_orders/single.rs`:
- Around line 117-133: The oracle fetch (crate::oracle::fetch_signed_context /
encode_oracle_body) is performed before early exits in execute_single_take,
causing unnecessary external POSTs when the function will immediately return
NoLiquidity or approval calldata; move the entire block that builds body and
calls fetch_signed_context (and the assignment to candidate.signed_context) to
after the price-cap rejection and approval short-circuit checks so it only runs
when proceeding to execution, ensuring oracle_url and taker are still available
in that later scope.

In `@crates/common/src/take_orders/candidates.rs`:
- Around line 84-117: The code is refetching oracle context with Address::ZERO
inside the quotes loop (via fetch_oracle_for_pair) and can bake wrong or missing
signatures into TakeOrderCandidate.signed_context which later becomes
TakeOrderConfigV4.signedContext; instead, stop refetching with a zero address
and either (A) thread the real counterparty address into this builder and pass
it into fetch_oracle_for_pair so the fetched signed_context is bound to the
correct taker, or (B) propagate the exact signed context produced by get_quotes
into try_build_candidate (and ultimately into TakeOrderCandidate.signed_context)
instead of calling fetch_oracle_for_pair here; update the loop around quotes and
the try_build_candidate call sites accordingly (also apply the same fix to the
analogous block at the later 128-147 region).

In `@crates/quote/src/oracle.rs`:
- Around line 89-145: The fetch_signed_context and fetch_signed_context_batch
functions currently POST to unvalidated URLs; before building the reqwest Client
and issuing the POST, validate the input URL: parse it and ensure it uses an
allowed scheme (https, optionally http only for explicit opt-in), resolve the
hostname to IP(s) and reject loopback, private (RFC1918), link-local, and other
non-public ranges, and/or enforce an explicit allowlist of hostnames or CIDRs
from configuration; perform these checks early in each function (e.g., right
after parsing the url param) and return an OracleError::InvalidInput or similar
if validation fails so that no request is sent to disallowed destinations.
- Around line 126-145: The function fetch_signed_context_batch must reject
responses whose array length doesn't match the requested batch size: after
deserializing response into Vec<OracleResponse> (the response variable) compare
response.len() against the expected request count (obtainable from the request
payload or caller-provided count) and if they differ return an OracleError
(e.g., BatchSizeMismatch with expected and got) instead of silently mapping to
SignedContextV1; update fetch_signed_context_batch to perform this length check
before mapping and returning the Vec<SignedContextV1>.

In `@crates/quote/src/order_quotes.rs`:
- Around line 97-123: The current code awaits
crate::oracle::fetch_signed_context inside the per-pair loop (the signed_context
logic using oracle_url, order_struct, input_index and output_index), causing
serial HTTP POSTs; instead, build and collect futures for all valid IO pairs
(calling fetch_signed_context with the encoded body) into a Vec or a
FuturesUnordered outside the per-pair await path and then await them in parallel
(e.g., futures::future::join_all or stream aggregation), mapping each result
back into the corresponding signed_context (handling Err by logging as done now)
so the orbit of fetch_signed_context calls runs concurrently rather than
sequentially.

---

Outside diff comments:
In `@crates/common/src/take_orders/config.rs`:
- Around line 87-95: The current tests never exercise non-empty signedContext
because candidates are built with make_simulation_candidate (which sets
signed_context: vec![]) and existing execute_single_take tests pass oracle_url =
None; add or update a unit test that constructs a simulation with at least one
leg whose candidate.signed_context is populated (or call the code path that
fetches oracle data so signedContext is set) and then after building
TakeOrderConfigV4 via the map (the orders variable produced from
sim.legs.iter().map(...) that creates TakeOrderConfigV4) assert that
config.orders[i].signedContext is non-empty; reference the TakeOrderConfigV4
construction and the execute_single_take test helper to locate where to inject
the non-empty signed context and the assertion.

In `@crates/quote/src/order_quotes.rs`:
- Around line 53-60: The code computes req_block_number using
ReadableClient::new_from_http_urls(...).get_block_number() but later (around the
oracle context fetch block at lines ~97-106) always requests live oracle state,
causing mismatches for oracle-backed orders when block_number is Some(...); fix
by detecting oracle-backed orders and either rejecting historical quotes (return
an error if block_number.is_some() and the order is oracle-backed) or by making
the oracle request block-aware: extend the oracle-context fetch to accept a
block/timestamp parameter and pass req_block_number there so the oracle snapshot
and chain state are consistent (locate req_block_number, the
ReadableClient::new_from_http_urls/get_block_number call, and the oracle-context
fetch block to implement the check or the parameter plumbing).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2b0bf5c2-a542-4450-9922-8083de28f885

📥 Commits

Reviewing files that changed from the base of the PR and between 22af026 and b48a03d.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (20)
  • crates/common/src/add_order.rs
  • crates/common/src/lib.rs
  • crates/common/src/oracle.rs
  • crates/common/src/parsed_meta.rs
  • crates/common/src/raindex_client/order_quotes.rs
  • crates/common/src/raindex_client/orders.rs
  • crates/common/src/raindex_client/take_orders/single.rs
  • crates/common/src/raindex_client/take_orders/single_tests.rs
  • crates/common/src/take_orders/candidates.rs
  • crates/common/src/take_orders/config.rs
  • crates/common/src/test_helpers.rs
  • crates/quote/Cargo.toml
  • crates/quote/src/lib.rs
  • crates/quote/src/oracle.rs
  • crates/quote/src/order_quotes.rs
  • crates/settings/src/gui.rs
  • crates/settings/src/order.rs
  • crates/settings/src/yaml/context.rs
  • lib/rain.interpreter
  • packages/ui-components/src/lib/components/detail/OrderDetail.svelte

Comment on lines +84 to +117
let oracle_url = {
#[cfg(target_family = "wasm")]
{
order.oracle_url()
}
#[cfg(not(target_family = "wasm"))]
{
order.oracle_url()
}
};

for quote in &quotes {
let signed_context = match &oracle_url {
Some(url) => {
fetch_oracle_for_pair(
url,
&order_v4,
quote.pair.input_index,
quote.pair.output_index,
Address::ZERO, // counterparty unknown at candidate building time
)
.await
}
None => vec![],
};

if let Some(candidate) = try_build_candidate(
orderbook,
&order_v4,
quote,
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 | 🔴 Critical

Don't bake Address::ZERO oracle signatures into candidates.

TakeOrderCandidate.signed_context is copied straight into TakeOrderConfigV4.signedContext in crates/common/src/take_orders/config.rs:87-96. Here Line 103 fetches the oracle payload for Address::ZERO, and Lines 137-146 silently downgrade fetch failures to vec![], so a candidate can carry signed context that is either bound to the wrong counterparty or missing entirely. Any oracle that includes the taker in its signature will then fail at preflight/execution, and even taker-agnostic oracles can drift from the quote generated on Line 72 because this path refetches context after quoting. Thread the real counterparty through this builder, or propagate the exact signed context used by get_quotes instead of refetching it here.

Also applies to: 128-147

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

In `@crates/common/src/take_orders/candidates.rs` around lines 84 - 117, The code
is refetching oracle context with Address::ZERO inside the quotes loop (via
fetch_oracle_for_pair) and can bake wrong or missing signatures into
TakeOrderCandidate.signed_context which later becomes
TakeOrderConfigV4.signedContext; instead, stop refetching with a zero address
and either (A) thread the real counterparty address into this builder and pass
it into fetch_oracle_for_pair so the fetched signed_context is bound to the
correct taker, or (B) propagate the exact signed context produced by get_quotes
into try_build_candidate (and ultimately into TakeOrderCandidate.signed_context)
instead of calling fetch_oracle_for_pair here; update the loop around quotes and
the try_build_candidate call sites accordingly (also apply the same fix to the
analogous block at the later 128-147 region).

@hardyjosh hardyjosh force-pushed the feat/oracle-url-yaml-parsing branch from b48a03d to 9abd25d Compare March 12, 2026 07:54
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

♻️ Duplicate comments (4)
crates/quote/src/order_quotes.rs (1)

100-120: ⚠️ Potential issue | 🟠 Major

Don't await the oracle POST inside the per-pair loop.

This makes one HTTP round trip per valid IO pair before the multicall even starts, so quote latency grows linearly with pair count. Collect these oracle requests and resolve them in parallel, or use the batch oracle path, before building signedContext.

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

In `@crates/quote/src/order_quotes.rs` around lines 100 - 120, The per-pair code
is performing an awaited HTTP POST via crate::oracle::fetch_signed_context
inside the loop that builds signed_context, causing sequential round-trips;
instead, collect the oracle requests when oracle_url.is_some() (capture
order_struct, input_index, output_index, Address::ZERO and the URL) into a
collection of futures (or IDs to use with the batch oracle path) before
constructing signedContext, then resolve them in parallel (e.g., spawn tasks or
use futures::future::join_all or call the batch oracle endpoint) and after they
complete, map the results back into the signed_context for each pair; update the
logic that currently awaits crate::oracle::fetch_signed_context in the loop
(referenced by signed_context, crate::oracle::fetch_signed_context, oracle_url,
order_struct, input_index, output_index, Address::ZERO) to use the
pre-fetched/parallelized results.
crates/common/src/add_order.rs (1)

135-140: ⚠️ Potential issue | 🟠 Major

Replace any existing oracle meta before pushing the deployment URL.

If additional_meta already contains a KnownMagic::RaindexSignedContextOracleV1, this appends a second oracle item. Downstream readers only extract a single oracle entry, so the earlier meta can shadow deployment.order.oracle_url and send quote/take flows to the wrong endpoint.

🛠️ Minimal fix
         let additional_meta = {
             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());
             }
             if meta.is_empty() {
🤖 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 135 - 140, When adding the
deployment.order.oracle_url into additional_meta, first remove any existing meta
items whose magic equals KnownMagic::RaindexSignedContextOracleV1 so you don't
append a duplicate; locate the block that builds additional_meta (uses
additional_meta.unwrap_or_default(), deployment.order.oracle_url,
RaindexSignedContextOracleV1::parse and oracle.to_meta_item()) and filter or
retain only non-oracle entries before pushing oracle.to_meta_item() so the new
oracle replaces any prior one.
crates/common/src/raindex_client/take_orders/single.rs (1)

117-132: ⚠️ Potential issue | 🟠 Major

Move the oracle POST behind the cheap exits.

Lines 117-132 run before the price-cap rejection at Lines 136-138 and the approval short-circuit at Lines 151-153, so execute_single_take can block on external I/O even when it will return early. Approval-first flows also pay this POST twice: once before NeedsApproval, then again on the retry that actually builds calldata. Fetch the signed context only once the function knows it is proceeding to config/simulation.

Also applies to: 136-153

🤖 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 117 -
132, The oracle POST (fetch_signed_context) is being called too early in
execute_single_take and can block or run twice; move the call that sets
candidate.signed_context (currently done when oracle_url is Some and using
crate::oracle::fetch_signed_context) to after the cheap exits (the price-cap
rejection check and the NeedsApproval short-circuit) so it only runs when the
function actually proceeds to config/simulation and calldata building, and
ensure the retry path does not re-post unnecessarily by fetching the signed
context once and reusing it for subsequent calldata construction.
crates/common/src/take_orders/candidates.rs (1)

72-80: ⚠️ Potential issue | 🔴 Critical

Don't freeze oracle context with a zero taker or empty fallback.

Line 79 binds the oracle request to Address::ZERO, and Lines 115-123 turn fetch failures into vec![]. build_take_orders_config_from_simulation later copies candidate.signed_context straight into TakeOrderConfigV4.signedContext, so this can send either the wrong signature or no signature at all to preflight/execution, with no oracle URL left on TakeOrderCandidate to recover later. Defer the fetch until the real taker is known, or carry the oracle URL forward instead of materializing signed_context here. It also POSTs before candidate filtering, so rejected pairs still pay the network round-trip.

Also applies to: 113-123

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

In `@crates/common/src/take_orders/candidates.rs` around lines 72 - 80, The code
currently calls fetch_oracle_for_pair early (binding Address::ZERO) and stores a
materialized signed_context (or vec![] on error) on TakeOrderCandidate, which
can yield wrong/missing signatures and causes unnecessary POSTs; instead, stop
calling fetch_oracle_for_pair here (remove the Address::ZERO call) and either
(A) defer fetching until you have the real taker in the path that builds
TakeOrderConfigV4 so fetch_oracle_for_pair is invoked with the real taker before
populating TakeOrderConfigV4.signedContext, or (B) preserve the oracle URL on
TakeOrderCandidate (e.g., keep oracle_url: Option<String>) and propagate that
into build_take_orders_config_from_simulation so the actual signed_context is
fetched there; also avoid swallowing errors into vec![]—return or propagate the
error so callers can decide, and ensure you only POST to the oracle after
candidate filtering so rejected pairs never trigger network requests (update
code paths referencing signed_context, fetch_oracle_for_pair,
TakeOrderCandidate, build_take_orders_config_from_simulation, and
TakeOrderConfigV4.signedContext).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/ui-components/src/lib/components/detail/OrderDetail.svelte`:
- Around line 215-222: OrderDetail.svelte renders untrusted data.oracleUrl
directly into an anchor href; restrict the link to http(s) schemes before
binding to href to avoid javascript:/data: URIs. Update the rendering so you
validate/sanitize data.oracleUrl (e.g., try to parse with the URL constructor or
check startsWith 'http://' or 'https://') and only set href when the scheme is
http or https; otherwise render the value as plain text (or disable the anchor)
and avoid creating a clickable link. Ensure you change the <a> binding where
data.oracleUrl is used so the href is conditionally set based on that
validation.

---

Duplicate comments:
In `@crates/common/src/add_order.rs`:
- Around line 135-140: When adding the deployment.order.oracle_url into
additional_meta, first remove any existing meta items whose magic equals
KnownMagic::RaindexSignedContextOracleV1 so you don't append a duplicate; locate
the block that builds additional_meta (uses additional_meta.unwrap_or_default(),
deployment.order.oracle_url, RaindexSignedContextOracleV1::parse and
oracle.to_meta_item()) and filter or retain only non-oracle entries before
pushing oracle.to_meta_item() so the new oracle replaces any prior one.

In `@crates/common/src/raindex_client/take_orders/single.rs`:
- Around line 117-132: The oracle POST (fetch_signed_context) is being called
too early in execute_single_take and can block or run twice; move the call that
sets candidate.signed_context (currently done when oracle_url is Some and using
crate::oracle::fetch_signed_context) to after the cheap exits (the price-cap
rejection check and the NeedsApproval short-circuit) so it only runs when the
function actually proceeds to config/simulation and calldata building, and
ensure the retry path does not re-post unnecessarily by fetching the signed
context once and reusing it for subsequent calldata construction.

In `@crates/common/src/take_orders/candidates.rs`:
- Around line 72-80: The code currently calls fetch_oracle_for_pair early
(binding Address::ZERO) and stores a materialized signed_context (or vec![] on
error) on TakeOrderCandidate, which can yield wrong/missing signatures and
causes unnecessary POSTs; instead, stop calling fetch_oracle_for_pair here
(remove the Address::ZERO call) and either (A) defer fetching until you have the
real taker in the path that builds TakeOrderConfigV4 so fetch_oracle_for_pair is
invoked with the real taker before populating TakeOrderConfigV4.signedContext,
or (B) preserve the oracle URL on TakeOrderCandidate (e.g., keep oracle_url:
Option<String>) and propagate that into build_take_orders_config_from_simulation
so the actual signed_context is fetched there; also avoid swallowing errors into
vec![]—return or propagate the error so callers can decide, and ensure you only
POST to the oracle after candidate filtering so rejected pairs never trigger
network requests (update code paths referencing signed_context,
fetch_oracle_for_pair, TakeOrderCandidate,
build_take_orders_config_from_simulation, and TakeOrderConfigV4.signedContext).

In `@crates/quote/src/order_quotes.rs`:
- Around line 100-120: The per-pair code is performing an awaited HTTP POST via
crate::oracle::fetch_signed_context inside the loop that builds signed_context,
causing sequential round-trips; instead, collect the oracle requests when
oracle_url.is_some() (capture order_struct, input_index, output_index,
Address::ZERO and the URL) into a collection of futures (or IDs to use with the
batch oracle path) before constructing signedContext, then resolve them in
parallel (e.g., spawn tasks or use futures::future::join_all or call the batch
oracle endpoint) and after they complete, map the results back into the
signed_context for each pair; update the logic that currently awaits
crate::oracle::fetch_signed_context in the loop (referenced by signed_context,
crate::oracle::fetch_signed_context, oracle_url, order_struct, input_index,
output_index, Address::ZERO) to use the pre-fetched/parallelized results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 135299e9-a7b4-4665-80cd-c4311ec13bc3

📥 Commits

Reviewing files that changed from the base of the PR and between b48a03d and 9abd25d.

📒 Files selected for processing (11)
  • crates/common/src/add_order.rs
  • crates/common/src/raindex_client/order_quotes.rs
  • crates/common/src/raindex_client/orders.rs
  • crates/common/src/raindex_client/take_orders/single.rs
  • crates/common/src/raindex_client/take_orders/single_tests.rs
  • crates/common/src/take_orders/candidates.rs
  • crates/quote/src/order_quotes.rs
  • crates/settings/src/gui.rs
  • crates/settings/src/order.rs
  • crates/settings/src/yaml/context.rs
  • packages/ui-components/src/lib/components/detail/OrderDetail.svelte

Comment on lines +215 to +222
<a
href={data.oracleUrl}
target="_blank"
rel="noopener noreferrer"
class="break-all text-blue-500 hover:underline"
>
{data.oracleUrl}
</a>
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

Restrict the Oracle link to http(s) before binding it to href.

data.oracleUrl comes from order metadata, not a trusted constant. Rendering it directly into an anchor keeps javascript:/data: payloads clickable if upstream validation ever misses one.

🔒 Suggested hardening
+						{`@const` safeOracleUrl = (() => {
+							try {
+								const url = new URL(data.oracleUrl);
+								return url.protocol === 'http:' || url.protocol === 'https:' ? url.href : null;
+							} catch {
+								return null;
+							}
+						})()}
-						<a
-							href={data.oracleUrl}
-							target="_blank"
-							rel="noopener noreferrer"
-							class="break-all text-blue-500 hover:underline"
-						>
-							{data.oracleUrl}
-						</a>
+						{`#if` safeOracleUrl}
+							<a
+								href={safeOracleUrl}
+								target="_blank"
+								rel="noopener noreferrer"
+								class="break-all text-blue-500 hover:underline"
+							>
+								{data.oracleUrl}
+							</a>
+						{:else}
+							<span class="break-all">{data.oracleUrl}</span>
+						{/if}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/ui-components/src/lib/components/detail/OrderDetail.svelte` around
lines 215 - 222, OrderDetail.svelte renders untrusted data.oracleUrl directly
into an anchor href; restrict the link to http(s) schemes before binding to href
to avoid javascript:/data: URIs. Update the rendering so you validate/sanitize
data.oracleUrl (e.g., try to parse with the URL constructor or check startsWith
'http://' or 'https://') and only set href when the scheme is http or https;
otherwise render the value as plain text (or disable the anchor) and avoid
creating a clickable link. Ensure you change the <a> binding where
data.oracleUrl is used so the href is conditionally set based on that
validation.

Josh Hardy added 10 commits March 17, 2026 08:47
… degrading

When oracle-url is present but the fetch fails (e.g. wrong response
format, timeout, network error), the quote now shows the oracle error
in the UI rather than proceeding without signed context and getting
an unhelpful out-of-bounds panic.
- URL validation (SSRF prevention): validate http/https scheme before POST
- Deduplicate oracle meta in add_order to prevent double entries
- Move oracle fetch below early exits in take-order flow (price-cap, approval)
- Document historical quote + live oracle limitation
- Validate batch response length matches request count
- Restrict oracle URL href to http(s) in Svelte UI
- Redact oracle URL from warning logs
- Remove redundant token equality check (outer loop already filters)
- Add URL validation unit tests
- Add explanatory comment for Address::ZERO counterparty
- Fix single-fetch doc comment to match actual JSON array contract
@hardyjosh hardyjosh self-assigned this Mar 23, 2026
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should have tests for the new UI elements in this file.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

))
}

#[allow(clippy::too_many_arguments)]
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a way that we can get rid of this one?

Josh Hardy added 3 commits March 23, 2026 09:27
- Refactor execute_single_take to reduce parameter count
  - Created TakeOrderExecutionParams and RpcContext structs
  - Removed #[allow(clippy::too_many_arguments)] attribute
  - Updated function signature from 8 to 3 parameters
  - Added create_execution_params helper for tests
  - Updated 3 test calls (12 more need updating)

- Add comprehensive UI tests for Oracle URL functionality
  - Tests Oracle section visibility based on oracleUrl presence
  - Tests clickable links for http/https URLs with proper security attributes
  - Tests XSS protection: non-http URLs render as safe plain text
  - Tests Oracle tooltip information display
- Add missing Address import in single_tests.rs
- Update execute_single_take call in orders.rs to use new signature
- Fix all 15 test function calls to use new parameter structs
- Apply cargo formatting to pass static checks

All static checks now pass ✅
@hardyjosh hardyjosh requested a review from findolor March 23, 2026 10:07
Josh Hardy and others added 4 commits March 24, 2026 08:38
- Simplify Oracle tooltip test to avoid complex DOM text matching
- All UI tests now pass (635 tests ✅)
- CI should now pass completely
- Apply prettier formatting to fix CI lint check
- This was the missing step causing CI to fail despite tests passing locally
@hardyjosh hardyjosh enabled auto-merge (squash) March 24, 2026 15:47
@hardyjosh hardyjosh disabled auto-merge March 24, 2026 18:11
@hardyjosh hardyjosh merged commit 2ee7128 into main Mar 24, 2026
18 checks passed
@github-actions
Copy link
Contributor

@coderabbitai assess this PR size classification for the totality of the PR with the following criterias and report it in your comment:

S/M/L PR Classification Guidelines:

This guide helps classify merged pull requests by effort and complexity rather than just line count. The goal is to assess the difficulty and scope of changes after they have been completed.

Small (S)

Characteristics:

  • Simple bug fixes, typos, or minor refactoring
  • Single-purpose changes affecting 1-2 files
  • Documentation updates
  • Configuration tweaks
  • Changes that require minimal context to review

Review Effort: Would have taken 5-10 minutes

Examples:

  • Fix typo in variable name
  • Update README with new instructions
  • Adjust configuration values
  • Simple one-line bug fixes
  • Import statement cleanup

Medium (M)

Characteristics:

  • Feature additions or enhancements
  • Refactoring that touches multiple files but maintains existing behavior
  • Breaking changes with backward compatibility
  • Changes requiring some domain knowledge to review

Review Effort: Would have taken 15-30 minutes

Examples:

  • Add new feature or component
  • Refactor common utility functions
  • Update dependencies with minor breaking changes
  • Add new component with tests
  • Performance optimizations
  • More complex bug fixes

Large (L)

Characteristics:

  • Major feature implementations
  • Breaking changes or API redesigns
  • Complex refactoring across multiple modules
  • New architectural patterns or significant design changes
  • Changes requiring deep context and multiple review rounds

Review Effort: Would have taken 45+ minutes

Examples:

  • Complete new feature with frontend/backend changes
  • Protocol upgrades or breaking changes
  • Major architectural refactoring
  • Framework or technology upgrades

Additional Factors to Consider

When deciding between sizes, also consider:

  • Test coverage impact: More comprehensive test changes lean toward larger classification
  • Risk level: Changes to critical systems bump up a size category
  • Team familiarity: Novel patterns or technologies increase complexity

Notes:

  • the assessment must be for the totality of the PR, that means comparing the base branch to the last commit of the PR
  • the assessment output must be exactly one of: S, M or L (single-line comment) in format of: SIZE={S/M/L}
  • do not include any additional text, only the size classification
  • your assessment comment must not include tips or additional sections
  • do NOT tag me or anyone else on your comment

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 24, 2026

Tip

For best results, initiate chat on the files or code changes.

SIZE=L

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.

3 participants