Conversation
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.
- 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
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
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
Two fixes for oracle-backed orders: 1. src/oracle/index.ts: The oracle server returns a JSON **array** of SignedContextV1 objects (per the upstream rain.orderbook spec). The previous code validated response.signer directly on the parsed JSON, which fails on arrays (typeof array === "object" but array.signer is undefined). Now unwraps the first element of the array before validating. 2. src/core/modes/intra/simulation.ts: The clear3() calls for both V3 and V4 order codepaths hardcoded aliceSignedContext=[] and bobSignedContext=[]. This means oracle-backed orders always reverted on-chain because the signed context was never passed to the verifier. Now threads through the actual signedContext from each order's takeOrder struct (populated during quoting by fetchOracleContext). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
alloy's `(T1, T2, T3, T4).abi_encode()` produces the encoding of a *single wrapping tuple* (first word is an offset to the tuple content), while viem's `encodeAbiParameters([T1, T2, T3, T4], [...])` produces the encoding of *four separate parameters* (first word is the offset to the first dynamic parameter). These are different ABI layouts. The oracle server uses alloy's `abi_decode()` which expects the wrapped form. Fix: encode all four fields as components of a single tuple parameter instead of four top-level parameters. Also: - Skip bounty task bytecode compilation in intra-orderbook mode when gasCoveragePercentage="0" (the task is never included in the withdraw call anyway, and computing it requires a dispair address which may not be available for V6 orderbooks whose registry doesn't expose I_INTERPRETER/I_STORE directly). - Make resolveVersionContracts resilient to V6 deployers that don't expose I_INTERPRETER/I_STORE (falls back to using the deployer address itself, with a warning). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WalkthroughImplements oracle-signed context integration for orders. Adds oracle URL extraction from metadata, health/cooloff tracking for oracle reliability, and conditional signed context fetching. Updates the simulator to handle oracle tasks based on gas coverage and includes oracle context in calldata. Extends GraphQL queries and type definitions to support oracle metadata. Changes
Sequence Diagram(s)sequenceDiagram
actor Client
participant OrderManager
participant QuoteService
participant OracleService
participant SharedState
participant Simulator
Client->>OrderManager: quoteOrder()
OrderManager->>QuoteService: quoteSingleOrder(orderDetails, client, state)
alt state provided
QuoteService->>OracleService: fetchOracleContext(orderDetails)
alt oracleUrl present and V4 order
OracleService->>OracleService: Check cooloff status
alt not in cooloff
OracleService->>OracleService: POST to oracle with ABI-encoded request
OracleService->>OracleService: Validate response & record success/failure
OracleService->>SharedState: Update oracleHealth map
OracleService->>orderDetails: Inject signedContext into takeOrder
else in cooloff
OracleService->>OracleService: Skip oracle call
end
end
alt error fetching context
QuoteService->>QuoteService: Log warning, continue
end
end
QuoteService->>QuoteService: Call viemClient.call() for quote
QuoteService->>Client: Return quote result
Client->>Simulator: setTransactionData()
Simulator->>Simulator: Build oracle task based on gasCoveragePercentage
alt gasCoveragePercentage === "0"
Simulator->>Simulator: Use zero-address placeholder task
else
Simulator->>Simulator: Compile oracle task on-chain
end
Simulator->>Simulator: Construct calldata with signedContext from orderDetails
Simulator->>Client: Return transaction data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 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 197-203: The fetch to the untrusted oracle URL should be guarded
to mitigate SSRF: add a URL validation step (e.g., an isAllowedOracleUrl helper)
and call it before the try/fetch in the code around the fetch(...) call in
src/oracle/index.ts; ensure the validator enforces https only, rejects hostnames
resolving to private/local ranges (localhost, 127.*, 10.*, 172.16-31.*,
192.168.*) and/or checks against a configured allowlist of trusted oracle
domains, and if validation fails throw or return an error instead of performing
the POST to the untrusted URL.
- Around line 73-81: isInCooloff currently mutates OracleHealthMap by resetting
state.cooloffUntil to 0 when expired; change it to be a pure predicate that only
reads state and returns true/false, remove the assignment, and add a new helper
clearExpiredCooloff(healthMap: OracleHealthMap, url: string) that checks the
same expiry and resets cooloffUntil when needed; update callers (e.g., places
that call isInCooloff) to call clearExpiredCooloff where they previously relied
on the side effect, or ensure recordOracleSuccess handles the reset on success.
- Around line 172-176: fetchSignedContext currently returns Result<any, string>;
change its signature to return Promise<Result<SignedContextV1Response, string>>
and use the concrete SignedContextV1/SignedContextV1Response types (alongside
existing OracleOrderRequest and OracleHealthMap) so callers get proper typing.
In the response-validation block, replace the generic Result.ok(...) with
Result.ok(item as SignedContextV1Response) (or construct a
SignedContextV1Response from item) and ensure any validation function references
SignedContextV1 to guarantee the shape before casting.
- Around line 233-242: The current SignedContextV1 check in the validation block
(before calling recordOracleFailure and returning Result.err) is too permissive;
update the validation in the same block to (1) verify signer matches an Ethereum
address regex (0x + 40 hex chars), (2) verify signature is hex (0x-prefixed hex
string of appropriate length, e.g., 130 chars for 65-byte signature or accept
generic 0x + even-length hex), and (3) verify each element of context is a
bytes32 hex string (0x + 64 hex chars); if any check fails call
recordOracleFailure(healthMap, url) and return Result.err with a clear message.
Use the existing symbols (recordOracleFailure, Result.err) and keep validation
deterministic and concise so malformed oracle responses are rejected early.
In `@src/order/quote.ts`:
- Around line 41-46: In quoteSingleOrderV3 remove the unnecessary V4-only oracle
fetch: delete the conditional block that calls fetchOracleContext.call(state,
orderDetails) and inspects oracleResult (the if (state) { const oracleResult =
await fetchOracleContext.call(state, orderDetails); if (oracleResult.isErr()) {
console.warn(...) } } block) because fetchOracleContext returns
Result.ok(undefined) for non-V4 orders; also clean up any now-unused
identifiers/imports (state, fetchOracleContext, oracleResult) in that file.
In `@src/state/contracts.ts`:
- Around line 97-112: The fallback that assigns addresses.dispair to interpreter
and store should only run for Rain V6; update the conditional around the
existing block that checks !interpreter || !store to also verify the runtime
registry/version equals 6 (e.g., check a variable like rainVersion === 6 or
registryVersion === 6), so for v4/v5 the original read failure surfaces instead
of silently falling back. Keep the same warning text and assignments to
addresses.dispair, interpreter, and store inside the now-scoped V6-only branch.
In `@src/state/index.ts`:
- Around line 227-228: The oracleHealth Map currently stores entries
indefinitely (oracleHealth: Map<string, { consecutiveFailures: number;
cooloffUntil: number }>) which can grow unbounded; add a lastAccessed timestamp
to the value shape (e.g., { consecutiveFailures, cooloffUntil, lastAccessed })
and update it whenever you read/write an entry, then implement a periodic
cleanup task (e.g., in the same module or state initializer) that iterates
oracleHealth and deletes entries that have been healthy for a long time or not
accessed for a TTL (or whose lastAccessed is older than threshold);
alternatively provide an LRU-eviction wrapper around oracleHealth to cap
size—ensure all code that touches oracleHealth (lookups in the health-check
logic) updates lastAccessed so stale entries can be removed.
🪄 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: 18170229-1aa5-4166-a891-a826e54af0c7
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (12)
src/core/modes/intra/simulation.tssrc/oracle/fetch.tssrc/oracle/index.tssrc/order/index.tssrc/order/quote.tssrc/order/types/index.tssrc/order/types/v3.tssrc/order/types/v4.tssrc/state/contracts.tssrc/state/index.tssrc/subgraph/query.tssrc/subgraph/types.ts
| export function isInCooloff(healthMap: OracleHealthMap, url: string): boolean { | ||
| const state = healthMap.get(url); | ||
| if (!state || state.cooloffUntil === 0) return false; | ||
| if (Date.now() >= state.cooloffUntil) { | ||
| state.cooloffUntil = 0; | ||
| return false; | ||
| } | ||
| return true; | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Side effect in read function: isInCooloff mutates state.
The function resets cooloffUntil to 0 when the cooloff period has expired (line 77). This side effect during a read operation can cause issues in concurrent scenarios and violates the principle of least surprise for a function named with is prefix (implying a pure predicate).
♻️ Suggested refactor to separate concerns
export function isInCooloff(healthMap: OracleHealthMap, url: string): boolean {
const state = healthMap.get(url);
if (!state || state.cooloffUntil === 0) return false;
- if (Date.now() >= state.cooloffUntil) {
- state.cooloffUntil = 0;
- return false;
- }
- return true;
+ return Date.now() < state.cooloffUntil;
}
+
+export function clearExpiredCooloff(healthMap: OracleHealthMap, url: string): void {
+ const state = healthMap.get(url);
+ if (state && state.cooloffUntil !== 0 && Date.now() >= state.cooloffUntil) {
+ state.cooloffUntil = 0;
+ }
+}Then call clearExpiredCooloff explicitly where needed, or let recordOracleSuccess handle the reset naturally on the next successful call.
📝 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.
| export function isInCooloff(healthMap: OracleHealthMap, url: string): boolean { | |
| const state = healthMap.get(url); | |
| if (!state || state.cooloffUntil === 0) return false; | |
| if (Date.now() >= state.cooloffUntil) { | |
| state.cooloffUntil = 0; | |
| return false; | |
| } | |
| return true; | |
| } | |
| export function isInCooloff(healthMap: OracleHealthMap, url: string): boolean { | |
| const state = healthMap.get(url); | |
| if (!state || state.cooloffUntil === 0) return false; | |
| return Date.now() < state.cooloffUntil; | |
| } | |
| export function clearExpiredCooloff(healthMap: OracleHealthMap, url: string): void { | |
| const state = healthMap.get(url); | |
| if (state && state.cooloffUntil !== 0 && Date.now() >= state.cooloffUntil) { | |
| state.cooloffUntil = 0; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/oracle/index.ts` around lines 73 - 81, isInCooloff currently mutates
OracleHealthMap by resetting state.cooloffUntil to 0 when expired; change it to
be a pure predicate that only reads state and returns true/false, remove the
assignment, and add a new helper clearExpiredCooloff(healthMap: OracleHealthMap,
url: string) that checks the same expiry and resets cooloffUntil when needed;
update callers (e.g., places that call isInCooloff) to call clearExpiredCooloff
where they previously relied on the side effect, or ensure recordOracleSuccess
handles the reset on success.
| export async function fetchSignedContext( | ||
| url: string, | ||
| request: OracleOrderRequest, | ||
| healthMap: OracleHealthMap, | ||
| ): Promise<Result<any, string>> { |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Return type uses any — consider a stricter type.
fetchSignedContext returns Result<any, string>, but the validated response has a known shape matching SignedContextV1. Using any bypasses type safety and allows misuse at call sites.
♻️ Define and use a typed return
+export interface SignedContextV1Response {
+ signer: string;
+ context: string[];
+ signature: string;
+}
+
export async function fetchSignedContext(
url: string,
request: OracleOrderRequest,
healthMap: OracleHealthMap,
-): Promise<Result<any, string>> {
+): Promise<Result<SignedContextV1Response, string>> {Then update the validation block to return the typed object:
return Result.ok(item as SignedContextV1Response);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/oracle/index.ts` around lines 172 - 176, fetchSignedContext currently
returns Result<any, string>; change its signature to return
Promise<Result<SignedContextV1Response, string>> and use the concrete
SignedContextV1/SignedContextV1Response types (alongside existing
OracleOrderRequest and OracleHealthMap) so callers get proper typing. In the
response-validation block, replace the generic Result.ok(...) with
Result.ok(item as SignedContextV1Response) (or construct a
SignedContextV1Response from item) and ensure any validation function references
SignedContextV1 to guarantee the shape before casting.
| try { | ||
| const response = await fetch(url, { | ||
| method: "POST", | ||
| headers: { "Content-Type": "application/octet-stream" }, | ||
| body, | ||
| signal: controller.signal, | ||
| }); |
There was a problem hiding this comment.
Consider validating the oracle URL to mitigate SSRF risks.
The url parameter comes from on-chain metadata (extractOracleUrl), but malicious orders could embed URLs targeting internal services. While the impact depends on deployment environment, consider:
- Restricting to HTTPS only
- Blocking private IP ranges (localhost, 10.x, 172.16-31.x, 192.168.x)
- Using an allowlist of known oracle domains if applicable
🛡️ Example URL validation
function isAllowedOracleUrl(url: string): boolean {
try {
const parsed = new URL(url);
if (parsed.protocol !== 'https:') return false;
// Block private IPs - basic check
const host = parsed.hostname;
if (host === 'localhost' || host.startsWith('127.') ||
host.startsWith('10.') || host.startsWith('192.168.')) {
return false;
}
return true;
} catch {
return false;
}
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/oracle/index.ts` around lines 197 - 203, The fetch to the untrusted
oracle URL should be guarded to mitigate SSRF: add a URL validation step (e.g.,
an isAllowedOracleUrl helper) and call it before the try/fetch in the code
around the fetch(...) call in src/oracle/index.ts; ensure the validator enforces
https only, rejects hostnames resolving to private/local ranges (localhost,
127.*, 10.*, 172.16-31.*, 192.168.*) and/or checks against a configured
allowlist of trusted oracle domains, and if validation fails throw or return an
error instead of performing the POST to the untrusted URL.
| if ( | ||
| typeof item !== "object" || | ||
| item === null || | ||
| typeof (item as any).signer !== "string" || | ||
| !Array.isArray((item as any).context) || | ||
| typeof (item as any).signature !== "string" | ||
| ) { | ||
| recordOracleFailure(healthMap, url); | ||
| return Result.err("Oracle response is not a valid SignedContextV1"); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Validation could be stricter for security-critical data.
The validation only checks that signer and signature are strings, and context is an array. For oracle-signed context that will be used on-chain:
signershould be a valid Ethereum address (0x + 40 hex chars)signatureshould be valid hexcontextitems should be valid bytes32 values
While the on-chain verifier will ultimately reject malformed data, early validation prevents wasted gas and provides clearer error messages.
🛡️ Optional stricter validation
+const isValidAddress = (s: string): boolean => /^0x[a-fA-F0-9]{40}$/.test(s);
+const isValidHex = (s: string): boolean => /^0x[a-fA-F0-9]*$/.test(s);
+const isValidBytes32 = (s: string): boolean => /^0x[a-fA-F0-9]{64}$/.test(s);
+
// Validate shape of single SignedContextV1
if (
typeof item !== "object" ||
item === null ||
- typeof (item as any).signer !== "string" ||
- !Array.isArray((item as any).context) ||
- typeof (item as any).signature !== "string"
+ !isValidAddress((item as any).signer) ||
+ !Array.isArray((item as any).context) ||
+ !(item as any).context.every(isValidBytes32) ||
+ !isValidHex((item as any).signature)
) {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/oracle/index.ts` around lines 233 - 242, The current SignedContextV1
check in the validation block (before calling recordOracleFailure and returning
Result.err) is too permissive; update the validation in the same block to (1)
verify signer matches an Ethereum address regex (0x + 40 hex chars), (2) verify
signature is hex (0x-prefixed hex string of appropriate length, e.g., 130 chars
for 65-byte signature or accept generic 0x + even-length hex), and (3) verify
each element of context is a bytes32 hex string (0x + 64 hex chars); if any
check fails call recordOracleFailure(healthMap, url) and return Result.err with
a clear message. Use the existing symbols (recordOracleFailure, Result.err) and
keep validation deterministic and concise so malformed oracle responses are
rejected early.
| if (state) { | ||
| const oracleResult = await fetchOracleContext.call(state, orderDetails); | ||
| if (oracleResult.isErr()) { | ||
| console.warn("Failed to fetch oracle context:", oracleResult.error); | ||
| } | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Unnecessary oracle fetch call for V3 orders.
fetchOracleContext is called in quoteSingleOrderV3, but it immediately returns Result.ok(undefined) for non-V4 orders (see src/oracle/fetch.ts line 22). While harmless, this adds a function call overhead for every V3 quote.
Consider removing this block from quoteSingleOrderV3 since oracle context is V4-only:
♻️ Suggested change
export async function quoteSingleOrderV3(
orderDetails: Pair,
viemClient: PublicClient,
state?: SharedState,
blockNumber?: bigint,
gas?: bigint,
) {
- if (state) {
- const oracleResult = await fetchOracleContext.call(state, orderDetails);
- if (oracleResult.isErr()) {
- console.warn("Failed to fetch oracle context:", oracleResult.error);
- }
- }
-
const { data } = await viemClient📝 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.
| if (state) { | |
| const oracleResult = await fetchOracleContext.call(state, orderDetails); | |
| if (oracleResult.isErr()) { | |
| console.warn("Failed to fetch oracle context:", oracleResult.error); | |
| } | |
| } | |
| export async function quoteSingleOrderV3( | |
| orderDetails: Pair, | |
| viemClient: PublicClient, | |
| state?: SharedState, | |
| blockNumber?: bigint, | |
| gas?: bigint, | |
| ) { | |
| const { data } = await viemClient |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/order/quote.ts` around lines 41 - 46, In quoteSingleOrderV3 remove the
unnecessary V4-only oracle fetch: delete the conditional block that calls
fetchOracleContext.call(state, orderDetails) and inspects oracleResult (the if
(state) { const oracleResult = await fetchOracleContext.call(state,
orderDetails); if (oracleResult.isErr()) { console.warn(...) } } block) because
fetchOracleContext returns Result.ok(undefined) for non-V4 orders; also clean up
any now-unused identifiers/imports (state, fetchOracleContext, oracleResult) in
that file.
| // In Rain V6, the "deployer" address from the rainlang registry may | ||
| // actually be the parser, which doesn't expose I_INTERPRETER/I_STORE. | ||
| // Fall back to using the dispair address itself for all three fields — | ||
| // the actual interpreter/store will be taken from the order's evaluable | ||
| // struct at execution time. This allows intra-orderbook clearing | ||
| // (which doesn't need the task deployer) to work without a "real" | ||
| // deployer address. | ||
| if (!interpreter || !store) { | ||
| console.warn( | ||
| `Could not read interpreter/store from dispair ${addresses.dispair} — ` + | ||
| `using fallback. Task bytecode generation will fail; set gasCoveragePercentage="0" ` + | ||
| `to skip bounty tasks.`, | ||
| ); | ||
| interpreter = addresses.dispair; | ||
| store = addresses.dispair; | ||
| } |
There was a problem hiding this comment.
Scope the deployer-as-interpreter/store fallback to V6 only.
Line 104 applies fallback for all versions, but the rationale is V6-specific. For v4/v5, this can silently convert a bad read/config into invalid interpreter/store addresses and defer failure to simulation/execution.
💡 Proposed fix
- if (!interpreter || !store) {
+ if (!interpreter || !store) {
+ if (version !== "v6") {
+ return undefined;
+ }
console.warn(
`Could not read interpreter/store from dispair ${addresses.dispair} — ` +
`using fallback. Task bytecode generation will fail; set gasCoveragePercentage="0" ` +
`to skip bounty tasks.`,
);
interpreter = addresses.dispair;
store = addresses.dispair;
}📝 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.
| // In Rain V6, the "deployer" address from the rainlang registry may | |
| // actually be the parser, which doesn't expose I_INTERPRETER/I_STORE. | |
| // Fall back to using the dispair address itself for all three fields — | |
| // the actual interpreter/store will be taken from the order's evaluable | |
| // struct at execution time. This allows intra-orderbook clearing | |
| // (which doesn't need the task deployer) to work without a "real" | |
| // deployer address. | |
| if (!interpreter || !store) { | |
| console.warn( | |
| `Could not read interpreter/store from dispair ${addresses.dispair} — ` + | |
| `using fallback. Task bytecode generation will fail; set gasCoveragePercentage="0" ` + | |
| `to skip bounty tasks.`, | |
| ); | |
| interpreter = addresses.dispair; | |
| store = addresses.dispair; | |
| } | |
| // In Rain V6, the "deployer" address from the rainlang registry may | |
| // actually be the parser, which doesn't expose I_INTERPRETER/I_STORE. | |
| // Fall back to using the dispair address itself for all three fields — | |
| // the actual interpreter/store will be taken from the order's evaluable | |
| // struct at execution time. This allows intra-orderbook clearing | |
| // (which doesn't need the task deployer) to work without a "real" | |
| // deployer address. | |
| if (!interpreter || !store) { | |
| if (version !== "v6") { | |
| return undefined; | |
| } | |
| console.warn( | |
| `Could not read interpreter/store from dispair ${addresses.dispair} — ` + | |
| `using fallback. Task bytecode generation will fail; set gasCoveragePercentage="0" ` + | |
| `to skip bounty tasks.`, | |
| ); | |
| interpreter = addresses.dispair; | |
| store = addresses.dispair; | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/state/contracts.ts` around lines 97 - 112, The fallback that assigns
addresses.dispair to interpreter and store should only run for Rain V6; update
the conditional around the existing block that checks !interpreter || !store to
also verify the runtime registry/version equals 6 (e.g., check a variable like
rainVersion === 6 or registryVersion === 6), so for v4/v5 the original read
failure surfaces instead of silently falling back. Keep the same warning text
and assignments to addresses.dispair, interpreter, and store inside the
now-scoped V6-only branch.
| /** Oracle endpoint health tracking for cooloff */ | ||
| oracleHealth: Map<string, { consecutiveFailures: number; cooloffUntil: number }> = new Map(); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Consider adding eviction for stale oracle health entries.
The oracleHealth map will grow unboundedly as new oracle URLs are encountered. Over long runtimes with many different oracle endpoints, this could lead to memory growth.
Consider periodically evicting entries that have been healthy for extended periods or haven't been accessed recently.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/state/index.ts` around lines 227 - 228, The oracleHealth Map currently
stores entries indefinitely (oracleHealth: Map<string, { consecutiveFailures:
number; cooloffUntil: number }>) which can grow unbounded; add a lastAccessed
timestamp to the value shape (e.g., { consecutiveFailures, cooloffUntil,
lastAccessed }) and update it whenever you read/write an entry, then implement a
periodic cleanup task (e.g., in the same module or state initializer) that
iterates oracleHealth and deletes entries that have been healthy for a long time
or not accessed for a TTL (or whose lastAccessed is older than threshold);
alternatively provide an LRU-eviction wrapper around oracleHealth to cap
size—ensure all code that touches oracleHealth (lookups in the health-check
logic) updates lastAccessed so stale entries can be removed.
Summary
Adds oracle signed context support to the solver, enabling orders that use off-chain oracle data (e.g., real-time equity prices from Alpaca) to be quoted and cleared on-chain.
Proven on Base mainnet
Router arb tx (oracle order ↔ Hydrex DEX pool):
https://basescan.org/tx/0x82c8beb40f07e3df0ea3b50736e0835fe4bf4f571de69e0e555236112fc02cf0
This transaction shows the solver:
What's new
Oracle module (
src/oracle/):extractOracleUrl()— extracts oracle URL from order meta by searching for theRaindexSignedContextOracleV1CBOR magic number (0xff7a1507ba4419ca)fetchSignedContext()— POSTs ABI-encoded(OrderV4, uint256, uint256, address)to the oracle endpoint, parses the JSON array response, validates theSignedContextV1shapeOracle integration in quote pipeline (
src/oracle/fetch.ts):quoteSingleOrderV3()/quoteSingleOrderV4()before the on-chain quotetakeOrder.struct.signedContextso the on-chain quote call includes itKey fixes in this PR:
abi_decode()which expects a wrapping tuple, while viem'sencodeAbiParametersproduces separate-parameter encoding. Fixed by wrapping all fields in a single tuple parameter.SignedContextV1objects. The solver now unwrapsresponse[0]before validation.clear3()calls in intra-orderbook mode were hardcodingaliceSignedContext=[], bobSignedContext=[]. Now threads through the actual signedContext from each order's takeOrder struct.Order lifecycle with oracle context
Test plan
npm run build— clean0xff7a1507ba4419ca)🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Improvements