Skip to content

feat: add signed context oracle support#436

Closed
hardyjosh wants to merge 13 commits into2026-03-27-v6-calldata-fixfrom
feat/oracle-support
Closed

feat: add signed context oracle support#436
hardyjosh wants to merge 13 commits into2026-03-27-v6-calldata-fixfrom
feat/oracle-support

Conversation

@hardyjosh
Copy link
Copy Markdown
Contributor

@hardyjosh hardyjosh commented Feb 23, 2026

Adds support for fetching signed context from oracle servers for orders that specify an oracle-url in their metadata. Changes: adds meta field to subgraph queries, new oracle module for fetching signed contexts (batch format per spec), wires oracle fetch into quoting pipeline. Note: extractOracleUrl is a placeholder pending SDK update.

Summary by CodeRabbit

  • New Features
    • Oracle integration: orders can include an extracted oracle URL; the system fetches and validates signed oracle context with timeout, health tracking, and temporary cooloff for failing endpoints.
    • Quoting workflow: quoting now requests oracle context (when present) and will surface oracle errors instead of proceeding.
  • Types / ABIs
    • Tightened signed-context shape and updated ABI shapes to match the new signed-context format.
  • Tests
    • Added comprehensive tests covering oracle fetching, URL extraction, health/cooloff, and quoting interactions.

1. Add meta field to subgraph queries for order discovery
2. Create oracle module with:
   - extractOracleUrl() placeholder for meta parsing
   - fetchSignedContext() for batch oracle requests
   - Support for batch format (array of contexts)
3. Wire oracle into quoting logic:
   - Extract oracle URL from order meta before quote2
   - Fetch signed context and inject into takeOrder struct
   - Graceful fallback on oracle failures
4. Ensure signed context flows through to takeOrdersConfig

The solver now automatically fetches oracle data for orders that
specify an oracle-url, enabling external data integration.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 23, 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

Adds an oracle integration: extracts oracle URL from order meta, tracks per-oracle health/cooloff, ABI-encodes and POSTs OrderV4 requests to fetch signed contexts with timeout/validation, injects signedContext into V4 orders before quoting, and extends subgraph queries to include order meta.

Changes

Cohort / File(s) Summary
Oracle core
src/oracle/fetch.ts, src/oracle/error.ts, src/oracle/types.ts, src/oracle/index.ts
New oracle modules: URL extraction (extractOracleUrl), constants/types (OracleConstants, OracleHealthMap, OracleOrderRequest, OracleSingleAbiParams), error types (OracleError, OracleErrorType), health/cooloff helpers, and fetchSignedContext(url, request, healthMap) with AbortController timeout, response validation, and health bookkeeping. Added fetchOracleContext(this: SharedState, orderDetails) that conditionally calls fetchSignedContext and injects returned SignedContextV2 into V4 order.`
Order quoting
src/order/quote.ts, src/order/index.ts
Quote entrypoints now accept/use state: SharedState (instead of raw client). Quote V3/V4 functions call fetchOracleContext.call(state, orderDetails) before on-chain quote calls; V4 injects oracle-signed context when returned. OrderManager.quoteOrder forwards this.state.
Order types & ABI
src/order/types/index.ts, src/order/types/v3.ts, src/order/types/v4.ts, src/common/abis/orderbook.ts
Added optional oracleUrl on PairBase and populated in PairV3.fromArgs/PairV4.fromArgs via extractOracleUrl(orderDetails.meta). Introduced SignedContextV2 type and tightened TakeOrderV4.signedContext to SignedContextV2[]. Updated ABIs to use SignedContextV2 in v5/v6 and adjusted related clear3 signatures.
Shared state
src/state/index.ts
SharedState now exposes oracleHealth: OracleHealthMap (initialized new Map()) for per-URL health/cooloff tracking.
Subgraph & types
src/subgraph/query.ts, src/subgraph/types.ts
GraphQL queries updated to request meta in orders; SgOrder type now includes optional meta?: string.
Tests
src/oracle/fetch.test.ts, src/oracle/index.test.ts, src/order/quote.test.ts, src/order/index.test.ts
Added comprehensive tests for oracle fetch, URL extraction, health/cooloff, and fetchOracleContext flows. Updated quote tests to pass SharedState and adjusted test fixtures to include oracleUrl field.
sequenceDiagram
    actor Client
    participant QuoteService as Quote Service
    participant OracleModule as Oracle Module
    participant OracleEndpoint as Oracle Endpoint
    Client->>QuoteService: quoteSingleOrderV4(order)
    QuoteService->>QuoteService: check order.meta
    alt meta present
        QuoteService->>OracleModule: extractOracleUrl(metaHex)
        OracleModule-->>QuoteService: url or undefined
        alt url found and not in cooloff
            QuoteService->>OracleModule: fetchSignedContext(url, request, state.oracleHealth)
            OracleModule->>OracleModule: ABI-encode OrderV4 request
            OracleModule->>OracleEndpoint: POST octet-stream (with timeout)
            OracleEndpoint-->>OracleModule: HTTP 200 + JSON
            OracleModule->>OracleModule: validate SignedContextV2 shape
            OracleModule-->>QuoteService: SignedContextV2
            QuoteService->>QuoteService: inject signedContext into orderDetails
        else url unknown / in cooloff / fetch fails
            OracleModule-->>QuoteService: OracleError (propagated)
        end
    end
    QuoteService->>QuoteService: perform on-chain quote via state.client.call(...)
    QuoteService-->>Client: return quote result
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 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 title 'feat: add signed context oracle support' accurately and concisely summarizes the main objective of the pull request, which introduces oracle support for fetching and injecting signed context into the order quoting pipeline.
Docstring Coverage ✅ Passed Docstring coverage is 80.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-support

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
Copy Markdown
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: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/oracle/index.ts`:
- Around line 89-93: Validate the oracle JSON before casting to
SignedContextV1[]: after parsing into contexts, iterate over each item and
ensure it has the required keys and types (e.g., that "signer" is a string,
"signature" is a string and/or bytes representation, and "context" is an object
or array as expected) and reject or throw a descriptive error for any malformed
entry; update the code around the contexts variable and the SignedContextV1
validation logic (the parsing block that populates contexts and the code that
later uses contexts for takeOrder) to perform these checks and sanitize/convert
fields as needed before returning or passing into takeOrder.
- Around line 36-41: The OracleOrderRequest currently types order as any;
replace that with the concrete OrderV4 type (e.g., use OrderV4) so the compiler
enforces structural compatibility during ABI encoding. Import or reference the
resolved OrderV4 type where OracleOrderRequest is declared, update the interface
field to order: OrderV4, and run TypeScript/tsc to fix any resulting type
mismatches in functions that construct or pass OracleOrderRequest objects
(adjust those call sites to produce a valid OrderV4).
- Around line 77-83: The fetch call that posts the oracle request (the statement
creating response = await fetch(url, { method: 'POST', headers: {...}, body:
ethers.getBytes(body) })) has no timeout and can hang; wrap the request with an
AbortController (or use AbortSignal.timeout(ms)) and pass its signal to fetch,
enforce a reasonable deadline (e.g., 5–10s), and ensure you abort the controller
on timeout; update the code around the fetch invocation to create controller =
new AbortController(), pass signal: controller.signal into fetch, use
setTimeout(() => controller.abort(), TIMEOUT_MS) (or AbortSignal.timeout) and
handle the abort in the existing error handling so the quoting pipeline fails
fast instead of blocking.
- Around line 12-18: The extractOracleUrl function currently logs a console.warn
on every invocation which will flood logs; change this by removing the per-call
console.warn or replace it with a one-time warning emitted via a module-level
flag (e.g., a let warned = false at module scope) so the message is logged only
once, or eliminate the log altogether and simply return null; update the
extractOracleUrl function to reference the module-level flag (or remove the
call) and ensure no per-invocation console.warn remains.
- Line 1: The code is using ethers v6 APIs but the project depends on ethers
v5.7.0; update the v6-specific calls to their v5 equivalents: replace uses of
ethers.AbiCoder.defaultAbiCoder() with ethers.utils.defaultAbiCoder and replace
ethers.getBytes(...) with ethers.utils.arrayify(...). Locate these calls in
src/oracle/index.ts (references around the symbols AbiCoder.defaultAbiCoder and
getBytes) and update any related variable names/usages so they call
ethers.utils.* accordingly, ensuring types/inputs remain the same.
- Around line 69-75: The code is ABI-encoding OrderV4 tuples using a hardcoded
tuple signature; replace that raw string with the canonical ABI constant from
the common ABI module. Import the Orderbook ABI export (e.g., Orderbook or
Orderbook.V5) and use its Order/OrderV4 type or parsed ABI parameter (via
parseAbiParameters if needed) in the abiCoder.encode call instead of the inline
tuple string so the encode uses the shared OrderV4 definition; update the import
and pass the referenced ABI constant where the current abiCoder.encode([...],
[tuples]) call appears.

In `@src/order/quote.ts`:
- Around line 42-63: Extract the duplicated oracle-fetch block into a private
helper named fetchOracleContext that accepts the orderDetails object, uses
extractOracleUrl(orderMeta) and fetchSignedContext(oracleUrl, [...]) to obtain
signed context, and updates orderDetails.takeOrder.struct.signedContext; then
replace the inline try/catch in quoteSingleOrderV3 and quoteSingleOrderV4 with a
call to await fetchOracleContext(orderDetails) wrapped in the same try/catch
that logs the warning on failure (preserve the same warning text), ensuring you
reference the existing functions fetchSignedContext and extractOracleUrl and the
same orderDetails.takeOrder.struct shape so behavior is unchanged.
- Around line 42-63: The oracle metadata access is wrong because
quoteSingleOrderV3 and quoteSingleOrderV4 receive a Pair (PairV3 | PairV4) that
doesn’t contain the original SgOrder.meta, so the fetch block using
(orderDetails as any).orderDetails?.meta is unreachable; fix by changing the
function(s) signature to accept the original SgOrder (or at least its
meta/oracle URL) alongside the Pair, or alternatively persist the SgOrder.meta
onto the Pair when the Pair is constructed so extractOracleUrl can read it; then
factor the duplicated fetch logic into a shared helper (e.g.,
fetchSignedContextForOrder) that calls extractOracleUrl and fetchSignedContext
and sets takeOrder.struct.signedContext (referencing
orderDetails.takeOrder.struct.signedContext) and use that helper from both
quoteSingleOrderV3 and quoteSingleOrderV4.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4928590 and 9fd313d.

📒 Files selected for processing (3)
  • src/oracle/index.ts
  • src/order/quote.ts
  • src/subgraph/query.ts

- Fix ethers v6 → v5 APIs (defaultAbiCoder, arrayify)
- Use ABI.Orderbook.V5.OrderV4 constant instead of hardcoded tuple string
- Add 5s timeout on oracle fetch via AbortController
- Validate SignedContextV1 shape on each response entry
- Extract fetchOracleContext helper to deduplicate quote logic
- Remove noisy console.warn from stub extractOracleUrl
- Type OracleOrderRequest.order properly instead of any
Copy link
Copy Markdown
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 (1)
src/order/quote.ts (1)

14-16: ⚠️ Potential issue | 🟠 Major

(orderDetails as any).meta will always be undefined — oracle fetch is dead code.

orderDetails is typed as Pair (PairV3 | PairV4), and as any is required because meta is not a member of the Pair type. Consequently, orderMeta is always undefined, the guard on Line 16 returns immediately every time, and oracle signed context is never fetched — making the entire oracle integration a no-op.

The previous form (orderDetails as any).orderDetails?.meta exhibited the same issue; the path was shortened but the root cause (the original SgOrder.meta is not surfaced on Pair) was not addressed. The fix requires one of:

  • Adding a meta?: string field to PairBase (or the variant types) and populating it when constructing the Pair from an SgOrder, or
  • Changing quoteSingleOrderV3/quoteSingleOrderV4 (and quoteSingleOrder) to accept the original SgOrder (or at least its oracle URL) alongside the Pair.
#!/bin/bash
# Verify whether Pair / PairBase / PairV3 / PairV4 expose a `meta` field
rg -n "meta" --type=ts src/order/types/ -C 2

# Also check if SgOrder carries meta and whether it's threaded into Pair construction
rg -n "meta" --type=ts src/subgraph/types.ts -C 2
rg -n "fromArgs\|new Pair\|PairV3\|PairV4" --type=ts src/order/types/ -A 10 | grep -A 5 "meta"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/order/quote.ts` around lines 14 - 16, fetchOracleContext is dead because
(orderDetails as any).meta is always undefined—the Pair type (PairBase / PairV3
/ PairV4) doesn’t expose SgOrder.meta—so oracle logic never runs. Fix by either:
1) add an optional meta?: string to PairBase (and relevant PairV3/PairV4) and
ensure the Pair factory/constructor that converts an SgOrder copies SgOrder.meta
into the Pair, replacing the (orderDetails as any).meta access; or 2) change
quoteSingleOrderV3 / quoteSingleOrderV4 (and quoteSingleOrder) to accept the
original SgOrder (or at least its oracle URL) alongside the Pair and pass that
through to fetchOracleContext, then remove the incorrect cast in
fetchOracleContext. Ensure fetchOracleContext reads the strongly typed field
(meta or provided oracle URL) so oracle fetching actually executes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/oracle/index.ts`:
- Around line 110-122: The current validation in the SignedContextV1 mapping
(the contexts constant in src/oracle/index.ts) only checks that entry.context is
an array but not that its items are strings; update the validator inside the
json.map where you check (entry as any).context to ensure Array.isArray((entry
as any).context) && ((entry as any).context).every((c: unknown) => typeof c ===
"string") so that non-string elements are rejected and the thrown Error message
still references the index i; keep returning entry as SignedContextV1 after the
stricter check.

---

Duplicate comments:
In `@src/order/quote.ts`:
- Around line 14-16: fetchOracleContext is dead because (orderDetails as
any).meta is always undefined—the Pair type (PairBase / PairV3 / PairV4) doesn’t
expose SgOrder.meta—so oracle logic never runs. Fix by either: 1) add an
optional meta?: string to PairBase (and relevant PairV3/PairV4) and ensure the
Pair factory/constructor that converts an SgOrder copies SgOrder.meta into the
Pair, replacing the (orderDetails as any).meta access; or 2) change
quoteSingleOrderV3 / quoteSingleOrderV4 (and quoteSingleOrder) to accept the
original SgOrder (or at least its oracle URL) alongside the Pair and pass that
through to fetchOracleContext, then remove the incorrect cast in
fetchOracleContext. Ensure fetchOracleContext reads the strongly typed field
(meta or provided oracle URL) so oracle fetching actually executes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd313d and 8fabe2e.

📒 Files selected for processing (2)
  • src/oracle/index.ts
  • src/order/quote.ts

Josh Hardy added 4 commits February 23, 2026 20:59
Replace ethers.utils.defaultAbiCoder/arrayify with viem's
encodeAbiParameters/hexToBytes. Use proper viem ABI parameter
definitions instead of string-based encoding.
- Up to 2 retries with exponential backoff (500ms, 1s)
- After 3 consecutive failures, oracle URL enters 5min cooloff
- During cooloff, requests to that URL are skipped immediately
- Cooloff resets on first successful response
- Invalid responses (bad shape, wrong length) also count as failures
- All configurable via module constants
No retries, no delays in the loop. Single attempt with 5s timeout —
if it fails, record the failure and move on. After 3 consecutive
failures the URL enters a 5min cooloff where it's skipped immediately
(no network call at all). This way one bad oracle can't block the
processing of other orders.
…ager

Extract oracle cooloff tracking from module-level singleton into an
OracleManager class. Instance lives on OrderManager, threaded through
to quote functions. This makes it properly scoped to the solver
instance lifecycle and testable.

- OracleManager class in src/oracle/manager.ts
- fetchSignedContext takes OracleManager as parameter
- OrderManager creates and owns the OracleManager instance
- OracleManager is optional in quote functions for backward compat
Copy link
Copy Markdown
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 (1)
src/oracle/index.ts (1)

150-162: ⚠️ Potential issue | 🟡 Minor

context array elements are not validated as strings.

Line 156 only verifies Array.isArray((entry as any).context) without checking that every element is a string. A malformed oracle response with non-string entries silently passes validation.

🛡️ Proposed fix
         !Array.isArray((entry as any).context) ||
+        !(entry as any).context.every((elem: unknown) => typeof elem === "string") ||
         typeof (entry as any).signature !== "string"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle/index.ts` around lines 150 - 162, The validation for
SignedContextV1 entries currently only checks Array.isArray((entry as
any).context) but doesn't ensure each element is a string; update the json.map
validation that creates contexts to assert that (entry as any).context is an
array of strings (e.g., use Array.isArray and .every(elem => typeof elem ===
"string")) and throw the same Error(`Oracle response[${i}] is not a valid
SignedContextV1`) if any element is non-string so the SignedContextV1 cast is
only applied after the full shape check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/oracle/index.ts`:
- Around line 32-43: OracleOrderRequest uses bare string for Ethereum-typed
fields which causes type errors when passed to encodeAbiParameters with
oracleBatchAbiParams; update the OracleOrderRequest interface so owner,
interpreter, store, and token are typed as viem Address (e.g. `0x${string}`),
and bytecode, vaultId, nonce (and any bytes/bytes32 fields) are typed as viem
Hex (e.g. `0x${string}`), or alternatively perform explicit casts to
`\`0x${string}\`` for those specific properties at the call site before calling
encodeAbiParameters; apply this change to the nested order fields referenced by
encodeAbiParameters and keep the already-casted counterparty treatment
consistent.

---

Duplicate comments:
In `@src/oracle/index.ts`:
- Around line 150-162: The validation for SignedContextV1 entries currently only
checks Array.isArray((entry as any).context) but doesn't ensure each element is
a string; update the json.map validation that creates contexts to assert that
(entry as any).context is an array of strings (e.g., use Array.isArray and
.every(elem => typeof elem === "string")) and throw the same Error(`Oracle
response[${i}] is not a valid SignedContextV1`) if any element is non-string so
the SignedContextV1 cast is only applied after the full shape check.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fabe2e and 5513fce.

📒 Files selected for processing (1)
  • src/oracle/index.ts

Josh Hardy added 4 commits February 23, 2026 21:13
Follow codebase conventions:
- Oracle health map lives on SharedState.oracleHealth
- fetchOracleContext is a standalone fn with this: SharedState,
  called via .call(state) like processOrder/findBestTrade
- Health helpers (isInCooloff, recordOracleSuccess/Failure) are
  plain exported functions operating on the health map
- No new classes, no module-level singletons
- quoteSingleOrder receives SharedState to thread through
- fetchSignedContext returns Result<SignedContextV1[], string>
- fetchOracleContext returns Result<void, string>
- Callers check .isErr() instead of try/catch
- Follows codebase convention for error handling
…terface

- OracleOrderRequest.order uses Order.V3 | Order.V4 from order/types
- OracleOrderRequest.counterparty typed as 0x
- Drop custom SignedContextV1 interface — signed context is already
  typed as any[] on TakeOrderV3/V4, and the response validation
  ensures the right shape at runtime
- fetchSignedContext returns Result<any[], string> matching the
  existing signedContext field type
- extractOracleUrl: implement CBOR meta parsing (was a stub returning null)
- Add oracleUrl field to PairBase, thread through V3/V4 fromArgs
- Add meta field to SgOrder type (already in subgraph query)
- fetchOracleContext: use pair.oracleUrl instead of dead (pair as any).meta
- Switch from batch to single request encoding to match oracle server spec
- Restrict oracle requests to V4 orders only
- Strip internal type discriminant before ABI encoding
Copy link
Copy Markdown
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

♻️ Duplicate comments (1)
src/oracle/index.ts (1)

209-215: ⚠️ Potential issue | 🟡 Minor

Response validator should enforce context element types.

Current validation accepts any array for context. Non-string elements can pass and fail later in ABI/use sites.

✅ Suggested tightening
     if (
         typeof json !== "object" ||
         json === null ||
         typeof (json as any).signer !== "string" ||
         !Array.isArray((json as any).context) ||
+        !(json as any).context.every((v: unknown) => typeof v === "string") ||
         typeof (json as any).signature !== "string"
     ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle/index.ts` around lines 209 - 215, The JSON validation currently
only checks that (json as any).context is an array but not that its elements are
strings; update the validation in the conditional around json/context in
src/oracle/index.ts to also require every element be a string (e.g., replace the
Array.isArray check with a combined check that context is an array and (json as
any).context.every((c) => typeof c === "string")). Ensure the new check is
included alongside the existing signer and signature checks so non-string
context elements fail validation early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/oracle/index.ts`:
- Around line 73-92: The cooloff expiry logic only clears cooloffUntil but
leaves consecutiveFailures at or above the threshold, causing immediate re-entry
to cooloff; update isInCooloff (or the cooloff-expiry path) to reset
state.consecutiveFailures = 0 when clearing the cooloff (i.e., when Date.now()
>= state.cooloffUntil set both state.cooloffUntil = 0 and
state.consecutiveFailures = 0) so that the failure streak is reset after a
cooloff expires; reference functions: isInCooloff, recordOracleFailure,
recordOracleSuccess, and the OracleHealthMap state object.
- Around line 171-179: The ABI encoding and conversions currently happen outside
the try/catch and can throw (encodeAbiParameters, BigInt, hexToBytes), so move
the block that builds orderStruct, calls
encodeAbiParameters(oracleSingleAbiParams, [...]), converts input/output indices
with BigInt, and hexToBytes into the existing try block (or wrap them in a new
try) inside the function so any thrown errors are caught; on catch return the
appropriate Err result consistent with the function's Result-based contract and
avoid letting exceptions bubble into quoting. Ensure you reference the same
symbols: request.order (strip type), oracleSingleAbiParams, encodeAbiParameters,
request.inputIOIndex/request.outputIOIndex, hexToBytes, and the body variable
when relocating the logic.
- Around line 163-191: The fetch call uses a caller-controlled url which allows
SSRF; before making the request in the function containing
isInCooloff/encodeAbiParameters/oracleSingleAbiParams/ORACLE_TIMEOUT_MS,
validate and sanitize `url`: reject non-HTTPS schemes, and either enforce a
configured allowlist of hostnames or at minimum parse the URL and block IP
addresses in private, loopback, link-local ranges and unresolved hostnames that
resolve to private ranges; throw or return a Result.err for disallowed URLs and
log the rejection so the fetch block never runs for untrusted addresses.

In `@src/order/quote.ts`:
- Around line 41-46: The oracle fetch in quoteSingleOrderV3 is a no-op for V3
orders and should be removed to avoid unnecessary async overhead: remove the
conditional block that calls fetchOracleContext.call(state, orderDetails) (the
if (state) { ... } block referencing fetchOracleContext in function
quoteSingleOrderV3) so the function no longer awaits a redundant oracle call;
alternatively, if you want to keep it for future-proofing, replace the block
with a short explanatory comment inside quoteSingleOrderV3 referencing
fetchOracleContext and that src/oracle/fetch.ts intentionally skips non-V4
orders to document the no-op behavior.

In `@src/order/types/v3.ts`:
- Line 125: The oracleUrl extraction for V3 orders is unnecessary since
fetchOracleContext returns early for non-V4 orders; update the V3 order
construction to set oracleUrl to null unconditionally (replace the conditional
extractOracleUrl(orderDetails.meta) usage) so you avoid the parsing
overhead—refer to the oracleUrl field in your V3 order creation and the
extractOracleUrl and fetchOracleContext functions when making the change.

---

Duplicate comments:
In `@src/oracle/index.ts`:
- Around line 209-215: The JSON validation currently only checks that (json as
any).context is an array but not that its elements are strings; update the
validation in the conditional around json/context in src/oracle/index.ts to also
require every element be a string (e.g., replace the Array.isArray check with a
combined check that context is an array and (json as any).context.every((c) =>
typeof c === "string")). Ensure the new check is included alongside the existing
signer and signature checks so non-string context elements fail validation
early.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fb3f30fa-4727-4e37-880f-448ea4784a85

📥 Commits

Reviewing files that changed from the base of the PR and between 5513fce and b6be597.

📒 Files selected for processing (9)
  • src/oracle/fetch.ts
  • src/oracle/index.ts
  • src/order/index.ts
  • src/order/quote.ts
  • src/order/types/index.ts
  • src/order/types/v3.ts
  • src/order/types/v4.ts
  • src/state/index.ts
  • src/subgraph/types.ts

Copy link
Copy Markdown
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: 11

♻️ Duplicate comments (1)
src/order/quote.ts (1)

39-42: 🧹 Nitpick | 🔵 Trivial

Oracle fetch in V3 is a no-op but adds async overhead.

The fetchOracleContext call will always return early for V3 orders (per src/oracle/index.ts line 23 which checks order.type !== Order.Type.V4). Consider removing this block from the V3 quote function to avoid unnecessary async overhead.

♻️ Proposed removal
 export async function quoteSingleOrderV3(
     orderDetails: Pair,
     state: SharedState,
     blockNumber?: bigint,
     gas?: bigint,
 ) {
-    const oracleResult = await fetchOracleContext.call(state, orderDetails);
-    if (oracleResult.isErr()) {
-        throw oracleResult.error;
-    }
-
     const { data } = await state.client
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/order/quote.ts` around lines 39 - 42, The V3 quote function is calling
fetchOracleContext (via fetchOracleContext.call(state, orderDetails)) which is a
no-op for V3 and adds unnecessary async overhead; remove the await call and the
subsequent oracleResult.isErr() error throw (the const oracleResult and the if
block) from the V3 quote code path, and ensure no remaining references to
oracleResult or its error handling remain in the function (preserve any upstream
logic that relied on successful oracle data by using existing V3-safe defaults).
Target symbols: fetchOracleContext, oracleResult, orderDetails, state, and the
V3 quote function name to locate and delete that block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/oracle/error.ts`:
- Around line 19-25: The JSDoc example uses the enum type instead of an enum
member; update both examples to pass a specific OracleErrorType member (e.g.,
OracleErrorType.FetchError) to the OracleError constructor so they type-check.
In the doc block for OracleError, replace occurrences of new OracleError("msg",
OracleErrorType) and new OracleError("msg", OracleErrorType, originalError) with
new OracleError("msg", OracleErrorType.FetchError) and new OracleError("msg",
OracleErrorType.FetchError, originalError) respectively, referencing the
OracleError constructor and the OracleErrorType enum member.

In `@src/oracle/fetch.ts`:
- Around line 144-153: The isInCooloff function resets only state.cooloffUntil
when the cooloff expires, leaving state.consecutiveFailures unchanged which
causes immediate re-entry into cooloff; update isInCooloff (operating on
OracleHealthMap / the state object) so that when Date.now() >=
state.cooloffUntil you set state.cooloffUntil = 0 and also reset
state.consecutiveFailures = 0 (or to the desired fresh-start value) before
returning false.
- Around line 15-16: The file-level docstring in src/oracle/fetch.ts incorrectly
says "SignedContextV1" while the function(s) in this module return
SignedContextV2; update the comment to mention "SignedContextV2" to match the
actual return type and function signature (replace the "SignedContextV1" text in
the POST description). Also scan this module for any other references to
SignedContextV1 and change them to SignedContextV2 so the docstring and code are
consistent.
- Around line 174-183: The validation in isValidSignedContextV2 currently only
checks that context is an array but not that its elements are strings; update
isValidSignedContextV2 to additionally verify that (value as
any).context.every((c) => typeof c === "string") so the function returns false
if any context element is not a string, keeping the other signer and signature
string checks intact; reference the isValidSignedContextV2 function and the
SignedContextV2 type when making this change.
- Around line 25-34: The fetchSignedContext function currently relies solely on
OracleConstants.isKnown (which is buggy) and must perform strict URL validation
before any network call: in fetchSignedContext validate the input URL using the
URL constructor, reject anything that is not https (scheme must be "https"),
resolve/validate the hostname to ensure it is not a loopback or private IP range
(e.g., 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and explicitly
block cloud metadata endpoints such as 169.254.169.254; if validation fails
return Result.err(new OracleError(..., OracleErrorType.Cooloff)). Keep
OracleConstants.isKnown as an additional check but do not rely on it as the sole
protection; perform validation early in fetchSignedContext (before any
DNS/fetch) and reuse OracleError/OracleErrorType for consistent error handling.
- Around line 42-51: The ABI encoding and conversions (encodeAbiParameters,
BigInt(request.inputIOIndex), BigInt(request.outputIOIndex), hexToBytes) must be
moved inside the existing try block so any exceptions are caught and returned as
a Result; specifically, remove the current pre-try lines that destructure
request.order into { type: _type, ...orderStruct } and call
encodeAbiParameters(OracleSingleAbiParams, [orderStruct, BigInt(...),
BigInt(...), request.counterparty]) and hexToBytes(encoded), and instead perform
that destructuring and all three operations inside the try block that handles
the request so thrown errors are caught and mapped to the function’s Result
flow.

In `@src/oracle/index.test.ts`:
- Line 76: Rename the test title string(s) that contain the typo "currectly" to
"correctly" (e.g., update the it(...) description "returns currectly call
fetchSignedContext when Order V4 when it returns ok" to "returns correctly call
fetchSignedContext when Order V4 when it returns ok"); search for other tests in
the file with the same misspelling and update them as well to keep naming
consistent (look for the exact phrase "currectly" in the repository and replace
with "correctly").
- Line 56: Rename the test description string in the failing test case so
"currectly" is corrected to "correctly"; locate the Jest test defined with
it("returns currectly call fetchSignedContext when Order V4 when it returns
error", ...) in src/oracle/index.test.ts and update the string to "returns
correctly call fetchSignedContext when Order V4 when it returns error".

In `@src/oracle/types.ts`:
- Around line 18-20: The isKnown function incorrectly calls KnownUrls.some with
the url string as the callback, causing a TypeError; change the check to
actually test membership (e.g., use KnownUrls.includes(url) or KnownUrls.some(u
=> u === url)) and keep the function signature isKnown(url: string): boolean so
callers like src/oracle/fetch.ts continue to work; ensure KnownUrls typing
matches the element type so the membership test compiles cleanly.

In `@src/order/index.ts`:
- Line 461: quoteSingleOrder (and its V3/V4 variants) should not let
fetchOracleContext failures abort the whole quote path: inside
src/order/quote.ts, wrap the fetchOracleContext call in a try/catch, catch
timeouts/5xx and other errors from fetchOracleContext, log the error, clear any
previously injected signedContext on the request/order object (e.g., remove or
set orderDetails.signedContext to undefined/null), and continue execution using
an empty context instead of rethrowing; ensure the rest of
quoteSingleOrder/quoteSingleOrderV3/quoteSingleOrderV4 code treats the missing
context as a valid fallback.

In `@src/order/quote.test.ts`:
- Around line 22-26: The SharedState stub used in quote tests is missing the
oracleHealth property so tests exercising the oracle-enabled quote path (where
orderDetails.oracleUrl is present) will fail; update the test fixture used in
quote.test.ts (the state object cast as SharedState) to include oracleHealth:
new Map() alongside client, and refactor the repeated stub creation (occurring
at the other occurrences around lines 76-80 and 114-118) into a shared helper or
factory function (e.g., makeTestState or sharedStateFixture) so all oracle-url
test cases exercise the real path instead of relying on a partial mock.

---

Duplicate comments:
In `@src/order/quote.ts`:
- Around line 39-42: The V3 quote function is calling fetchOracleContext (via
fetchOracleContext.call(state, orderDetails)) which is a no-op for V3 and adds
unnecessary async overhead; remove the await call and the subsequent
oracleResult.isErr() error throw (the const oracleResult and the if block) from
the V3 quote code path, and ensure no remaining references to oracleResult or
its error handling remain in the function (preserve any upstream logic that
relied on successful oracle data by using existing V3-safe defaults). Target
symbols: fetchOracleContext, oracleResult, orderDetails, state, and the V3 quote
function name to locate and delete that block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 903b3822-8d46-4f59-9fbd-ddefffd0730e

📥 Commits

Reviewing files that changed from the base of the PR and between b6be597 and d983c49.

📒 Files selected for processing (15)
  • src/common/abis/orderbook.ts
  • src/oracle/error.ts
  • src/oracle/fetch.test.ts
  • src/oracle/fetch.ts
  • src/oracle/index.test.ts
  • src/oracle/index.ts
  • src/oracle/types.ts
  • src/order/index.test.ts
  • src/order/index.ts
  • src/order/quote.test.ts
  • src/order/quote.ts
  • src/order/types/index.ts
  • src/order/types/v3.ts
  • src/order/types/v4.ts
  • src/state/index.ts

Comment on lines +25 to +34
export async function fetchSignedContext(
url: string,
request: OracleOrderRequest,
healthMap: OracleHealthMap,
): Promise<Result<SignedContextV2, OracleError>> {
if (!OracleConstants.isKnown(url)) {
return Result.err(
new OracleError(`Oracle ${url} is unknown, skipping`, OracleErrorType.Cooloff),
);
}
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

Security: No URL validation before fetch — potential SSRF risk.

The function calls OracleConstants.isKnown(url) as a gate, but isKnown is buggy (always throws). Even if fixed, a compromised order metadata could inject internal network URLs (localhost, private IPs, cloud metadata endpoints).

Consider adding URL validation to block:

  • Non-HTTPS schemes
  • Private/loopback IP ranges
  • Cloud metadata endpoints (169.254.169.254)
🔒 Proposed hardening
 export async function fetchSignedContext(
     url: string,
     request: OracleOrderRequest,
     healthMap: OracleHealthMap,
 ): Promise<Result<SignedContextV2, OracleError>> {
+    // Validate URL scheme and host
+    let parsed: URL;
+    try {
+        parsed = new URL(url);
+    } catch {
+        return Result.err(new OracleError("Invalid oracle URL", OracleErrorType.Cooloff));
+    }
+    if (parsed.protocol !== "https:") {
+        return Result.err(new OracleError("Oracle URL must use HTTPS", OracleErrorType.Cooloff));
+    }
+
     if (!OracleConstants.isKnown(url)) {
📝 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
export async function fetchSignedContext(
url: string,
request: OracleOrderRequest,
healthMap: OracleHealthMap,
): Promise<Result<SignedContextV2, OracleError>> {
if (!OracleConstants.isKnown(url)) {
return Result.err(
new OracleError(`Oracle ${url} is unknown, skipping`, OracleErrorType.Cooloff),
);
}
export async function fetchSignedContext(
url: string,
request: OracleOrderRequest,
healthMap: OracleHealthMap,
): Promise<Result<SignedContextV2, OracleError>> {
// Validate URL scheme and host
let parsed: URL;
try {
parsed = new URL(url);
} catch {
return Result.err(new OracleError("Invalid oracle URL", OracleErrorType.Cooloff));
}
if (parsed.protocol !== "https:") {
return Result.err(new OracleError("Oracle URL must use HTTPS", OracleErrorType.Cooloff));
}
if (!OracleConstants.isKnown(url)) {
return Result.err(
new OracleError(`Oracle ${url} is unknown, skipping`, OracleErrorType.Cooloff),
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/oracle/fetch.ts` around lines 25 - 34, The fetchSignedContext function
currently relies solely on OracleConstants.isKnown (which is buggy) and must
perform strict URL validation before any network call: in fetchSignedContext
validate the input URL using the URL constructor, reject anything that is not
https (scheme must be "https"), resolve/validate the hostname to ensure it is
not a loopback or private IP range (e.g., 127.0.0.0/8, 10.0.0.0/8,
172.16.0.0/12, 192.168.0.0/16) and explicitly block cloud metadata endpoints
such as 169.254.169.254; if validation fails return Result.err(new
OracleError(..., OracleErrorType.Cooloff)). Keep OracleConstants.isKnown as an
additional check but do not rely on it as the sole protection; perform
validation early in fetchSignedContext (before any DNS/fetch) and reuse
OracleError/OracleErrorType for consistent error handling.

@rouzwelt rouzwelt changed the base branch from master to 2026-03-27-v6-calldata-fix April 3, 2026 17:27
@rouzwelt
Copy link
Copy Markdown
Collaborator

moved to #438

@rouzwelt rouzwelt closed this Apr 10, 2026
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