Skip to content

Implement POST /v1/order/dca endpoint, stub solver#36

Open
findolor wants to merge 4 commits intomainfrom
2026-02-16-implement-order-deploy
Open

Implement POST /v1/order/dca endpoint, stub solver#36
findolor wants to merge 4 commits intomainfrom
2026-02-16-implement-order-deploy

Conversation

@findolor
Copy link
Collaborator

@findolor findolor commented Feb 16, 2026

Dependent PRs

Chained PRs

Motivation

See issues:

Implements the DCA order deployment endpoint. The solver endpoint is stubbed with todo!() pending strategy spec.

Solution

  • POST /v1/order/dca — Accepts DeployDcaOrderRequest (input/output tokens, budget, period, start/floor IO ratios, optional vault IDs), configures a DCA order via the GUI, and returns deployment calldata with any required token approvals. GUI field bindings match the auction-dca.rain strategy (input, output, amount-per-epoch, time-per-amount-epoch, initial-io, baseline).
  • POST /v1/order/solver — Route registered and visible in OpenAPI but handler is todo!() until the solver strategy spec is known.
  • OrderDeployer trait — Abstracts DotrainOrderGui methods (set_select_token, set_field_value, set_deposit, set_vault_id, get_deployment_transaction_args) behind a trait so process functions can be unit-tested without network/WASM. RealOrderDeployer wraps the concrete GUI; MockOrderDeployer is used in tests.
  • helpers.rs — Shared map_deployment_to_response converts DeploymentTransactionArgs to DeployOrderResponse, decoding approval calldata amounts.
  • RaindexProvider::run_with_registry — New method to execute closures with a DotrainRegistry on a dedicated thread (registry is !Send).
  • Added Clone derive to ApiError to support mock trait implementations.
  • Tests: Auth (401), registry failure (500), success path, success with approvals, success with vault IDs, and deployment args failure — for DCA.

Checks

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

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

fix #22

Summary by CodeRabbit

  • New Features

    • Implemented DCA order deployment with support for token selection, budget configuration, periods, and vault management
    • Automated approval handling for deployment transactions
    • Added required owner field to order deployment requests
  • Chores

    • Updated orderbook dependencies and submodule references

Replace todo!() stubs with full implementations that use DotrainOrderGui
via the registry to configure tokens, fields, deposits, and vault IDs,
then build deployment transaction calldata and approval data.
Introduce OrderDeployer trait to abstract DotrainOrderGui methods,
enabling unit tests for DCA and solver deploy success paths without
requiring network/WASM dependencies.
@findolor findolor self-assigned this Feb 16, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

Implements the POST /v1/order/dca endpoint for deploying DCA orders by adding dependencies, introducing an OrderDeployer trait abstraction, wiring GUI-based deployment logic through a registry, and providing helper functions to map deployment arguments to API responses.

Changes

Cohort / File(s) Summary
Dependencies
Cargo.toml, lib/rain.orderbook
Added rain_orderbook_app_settings and rain_orderbook_bindings path dependencies; updated submodule pointer to incorporate new bindings support.
Registry Runtime Support
src/raindex/config.rs
Added run_with_registry method to RaindexProvider enabling async operations with a DotrainRegistry within a dedicated thread-isolated Tokio runtime.
Deployment Abstraction
src/routes/order/mod.rs
Introduced OrderDeployer trait defining pluggable deployment operations (token selection, field setting, deposits, vault ID configuration, transaction arg retrieval); implemented RealOrderDeployer to delegate to DotrainOrderGui; added test scaffolding with MockOrderDeployer.
DCA Deployment Implementation
src/routes/order/deploy_dca.rs
Implemented process_deploy_dca function orchestrating GUI field population (tokens, budget, period, vault IDs), fund deposits, and deployment arg retrieval; updated post_order_dca handler to use registry-provided GUI; added comprehensive tests for authorization, vault handling, and error scenarios.
Deployment Response Helpers
src/routes/order/helpers.rs
Introduced map_deployment_to_response and decode_approval_amount functions to convert DeploymentTransactionArgs into DeployOrderResponse by decoding approval calldata; includes unit tests for typical and edge cases.
Request Type Extensions
src/types/order.rs
Added owner field (Address type) to DeployDcaOrderRequest and DeploySolverOrderRequest structures.
Solver Route Placeholder
src/routes/order/deploy_solver.rs
Removed runtime instrumentation and processing logic; converted post_order_solver to a no-op todo!() placeholder.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as post_order_dca
    participant Registry
    participant GUI as OrderDeployer
    participant Helper
    participant Response

    Client->>Handler: POST /order/dca (DeployDcaOrderRequest)
    Handler->>Registry: get_gui()
    Registry-->>Handler: DotrainOrderGui
    Handler->>GUI: set_select_token(input_token)
    GUI-->>Handler: Result
    Handler->>GUI: set_select_token(output_token)
    GUI-->>Handler: Result
    Handler->>GUI: set_field_value(budget)
    GUI-->>Handler: Result
    Handler->>GUI: set_field_value(period, period_unit)
    GUI-->>Handler: Result
    Handler->>GUI: set_deposit(token, amount)
    GUI-->>Handler: Result
    Handler->>GUI: set_vault_id(input/output)
    GUI-->>Handler: Result
    Handler->>GUI: get_deployment_transaction_args(owner)
    GUI-->>Handler: DeploymentTransactionArgs
    Handler->>Helper: map_deployment_to_response(args)
    Helper-->>Handler: DeployOrderResponse
    Handler->>Response: Json<DeployOrderResponse>
    Response-->>Client: Success Response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #2: Adds Rocket-based stubs and types for the order API surface; this PR implements the actual deployment logic and helpers that integrate with those types.
  • PR #33: Modified src/raindex/config.rs to add run_with_client helper; this PR extends the pattern with run_with_registry for registry-scoped async operations.
  • PR #35: Touches the order routing module and deploy DCA route implementation; directly related through shared endpoint wiring and deployment infrastructure.

Suggested reviewers

  • hardyjosh
  • 0xgleb

Poem

🐰 Hoppy deployment, tokens dance with glee,
Registry GUI, a symphony of three,
DCA orders bloom from vault to vault,
Helper functions smooth each deployment's jolt! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: implementing the DCA order endpoint and stubbing the solver endpoint.
Linked Issues check ✅ Passed The PR fulfills issue #22 requirements by implementing POST /v1/order/dca with DeployDcaOrderRequest/DeployOrderResponse and integrating the Rain Orderbook SDK.
Out of Scope Changes check ✅ Passed The post_order_solver endpoint was stubbed with todo!() instead of being fully implemented, which aligns with the PR objective to stub it pending strategy spec.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2026-02-16-implement-order-deploy

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.

@findolor findolor requested review from 0xgleb and hardyjosh February 16, 2026 13:53
@findolor
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@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.

🧹 Nitpick comments (5)
src/raindex/config.rs (1)

45-76: Unreachable WorkerError::Api arm in error mapping.

On line 67, the closure result is always wrapped in Ok(...), so WorkerError::Api is never produced inside run_with_registry. The match arm on line 74 is dead code. Unlike run_with_client where get_raindex_client can fail with WorkerError::Api, this method has no such fallible step.

Consider simplifying the error handling to only match the reachable variant, or alternatively, make the closure return a Result if you anticipate needing error propagation from within the worker thread in the future.

♻️ Suggested simplification
-        rx.await
-            .map_err(|_| RaindexProviderError::WorkerPanicked)?
-            .map_err(|e| match e {
-                WorkerError::RuntimeInit(e) => RaindexProviderError::RegistryRuntimeInit(e),
-                WorkerError::Api(e) => RaindexProviderError::RegistryLoad(e),
-            })
+        let (tx, rx) = tokio::sync::oneshot::channel::<Result<T, std::io::Error>>();
+
+        std::thread::spawn(move || {
+            let runtime = match tokio::runtime::Builder::new_current_thread()
+                .enable_all()
+                .build()
+            {
+                Ok(rt) => rt,
+                Err(error) => {
+                    tracing::error!(error = %error, "failed to build registry runtime");
+                    let _ = tx.send(Err(error));
+                    return;
+                }
+            };
+
+            let _ = tx.send(Ok(runtime.block_on(f(registry))));
+        });
+
+        rx.await
+            .map_err(|_| RaindexProviderError::WorkerPanicked)?
+            .map_err(RaindexProviderError::RegistryRuntimeInit)
src/routes/order/helpers.rs (1)

20-32: Spender is hard-coded to orderbook_address instead of decoded from calldata.

decode_approval_amount already decodes the full approveCall (which includes spender), but the decoded spender is discarded. The Approval.spender is then set to args.orderbook_address on line 26, which would be incorrect if the SDK ever emits an approval to a different contract (e.g., a router or permit2).

Consider extracting both spender and amount from the decoded calldata for correctness:

♻️ Suggested refactor
-fn decode_approval_amount(calldata: &Bytes) -> Result<String, ApiError> {
+struct DecodedApproval {
+    pub spender: alloy::primitives::Address,
+    pub amount: String,
+}
+
+fn decode_approval(calldata: &Bytes) -> Result<DecodedApproval, ApiError> {
     let decoded = approveCall::abi_decode(calldata).map_err(|e| {
         tracing::error!(error = %e, "failed to decode approval calldata");
         ApiError::Internal("failed to decode approval calldata".into())
     })?;
-    Ok(decoded.amount.to_string())
+    Ok(DecodedApproval {
+        spender: decoded.spender,
+        amount: decoded.amount.to_string(),
+    })
 }

Then in map_deployment_to_response:

         .map(|a| {
+            let decoded = decode_approval(&a.calldata)?;
             Ok(Approval {
                 token: a.token,
-                spender: args.orderbook_address,
+                spender: decoded.spender,
                 symbol: a.symbol.clone(),
                 approval_data: a.calldata.clone(),
-                amount: decode_approval_amount(&a.calldata)?,
+                amount: decoded.amount,
             })
         })
src/routes/order/deploy_solver.rs (1)

12-18: DEPOSIT_TOKEN_KEY duplicates OUTPUT_TOKEN_KEY — add a clarifying comment.

Both OUTPUT_TOKEN_KEY and DEPOSIT_TOKEN_KEY resolve to "output-token". While this is semantically correct (deposits go to the output token for solver orders), the duplication without context may confuse future readers about whether they should diverge.

A brief comment would clarify the intent.

src/routes/order/deploy_dca.rs (1)

48-66: Consider extracting the shared registry→GUI→process pattern.

The handler body (registry interaction, GUI creation, error mapping, double-? unwrap) is nearly identical between post_order_dca and post_order_solver. This is a small amount of boilerplate now but will grow if more order types are added.

♻️ Sketch of a shared helper
// In a shared location (e.g., helpers.rs or mod.rs):
async fn with_order_gui<F, Fut>(
    raindex: &crate::raindex::RaindexProvider,
    order_key: &'static str,
    deployment_key: &'static str,
    f: F,
) -> Result<DeployOrderResponse, ApiError>
where
    F: FnOnce(RealOrderDeployer) -> Fut + Send + 'static,
    Fut: std::future::Future<Output = Result<DeployOrderResponse, ApiError>>,
{
    raindex
        .run_with_registry(move |registry| async move {
            let gui = registry
                .get_gui(order_key.to_string(), deployment_key.to_string(), None, None)
                .await
                .map_err(|e| {
                    tracing::error!(error = %e, "failed to create GUI");
                    ApiError::Internal("failed to initialize order configuration".into())
                })?;
            let mut gui = RealOrderDeployer { gui };
            f(gui).await
        })
        .await
        .map_err(ApiError::from)?
}
src/routes/order/mod.rs (1)

439-492: Mock doesn't capture call arguments — limits assertiveness of unit tests.

MockOrderDeployer returns the same canned result for every call to set_field_value, set_select_token, etc., regardless of arguments. This means tests can't verify that process_deploy_dca passes the correct keys and values to each method — only that the happy/error paths complete as expected.

Consider adding argument capture (e.g., Vec<(String, String)> fields) if you want to assert that the correct GUI fields are being populated with the expected request values. This would strengthen the unit tests significantly without much additional complexity.

@findolor findolor mentioned this pull request Feb 16, 2026
4 tasks
Base automatically changed from 2026-02-16-implement-order-cancel to main February 24, 2026 05:24
Aligns field names with the auction-dca.rain strategy spec and converts
period + unit into a single time-per-amount-epoch seconds value.
Strategy spec for solver orders is not yet known; replace the
implementation with todo!() so the route stays registered in OpenAPI
but is not callable until properly implemented.
@findolor findolor changed the title Implement POST /v1/order/dca and POST /v1/order/solver endpoints Implement POST /v1/order/dca endpoint, stub solver Feb 24, 2026
Copy link

@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: 2

🧹 Nitpick comments (3)
src/routes/order/mod.rs (2)

413-498: mock_approve_calldata is duplicated — consider consolidating into test_fixtures.

mock_approve_calldata (lines 416–418) is defined in test_fixtures here, but a copy also exists in the #[cfg(test)] block of src/routes/order/helpers.rs (snippet line 50–52). Both have identical logic. Since test_fixtures is already pub(crate), making the version here pub(crate) fn mock_approve_calldata(...) and removing the duplicate from helpers.rs eliminates the redundancy.

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

In `@src/routes/order/mod.rs` around lines 413 - 498, The duplicate
mock_approve_calldata causes redundancy; make the one in this module publicly
available to the crate and remove the copy in helpers.rs. Change the local
function signature to pub(crate) fn mock_approve_calldata(spender: Address,
amount: alloy::primitives::U256) -> Bytes (keeping the same body) inside the
test_fixtures module and delete the identical implementation in the #[cfg(test)]
block of src/routes/order/helpers.rs, then update any call sites to use the
single test_fixtures::mock_approve_calldata if necessary.

96-148: RealOrderDeployer implementation and error mapping look correct; consider narrowing gui field visibility.

All method delegations include tracing::error! on failures and map to appropriate ApiError variants (BadRequest for user-correctable inputs, Internal for deployment args). The only minor point: pub gui: DotrainOrderGui (line 93) exposes the field publicly within the crate. Since the only construction site in deploy_dca.rs uses struct literal syntax, a pub(super) or pub(crate) visibility (or a small constructor) would be more encapsulated.

♻️ Optional: restrict field visibility
 pub(crate) struct RealOrderDeployer {
-    pub gui: DotrainOrderGui,
+    pub(crate) gui: DotrainOrderGui,
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/order/mod.rs` around lines 96 - 148, The RealOrderDeployer's gui
field is currently publicly exposed (pub gui: DotrainOrderGui); restrict its
visibility to reduce coupling by changing its visibility to pub(super) or
pub(crate) or by making it private and adding a small constructor associated
function (e.g., RealOrderDeployer::new(gui: DotrainOrderGui)) used by the
deploy_dca.rs construction site; update any creation sites in deploy_dca.rs to
call the new constructor or the adjusted visibility.
src/raindex/config.rs (1)

71-79: WorkerError::Api arm in the error mapper is unreachable dead code.

run_with_registry always sends Ok(runtime.block_on(f(registry))) (line 71); it never constructs WorkerError::Api(...). The WorkerError::Api(e) => RaindexProviderError::RegistryLoad(e) arm at line 78 is therefore never reached. run_with_client propagates API errors through that variant because it explicitly calls get_raindex_client() and may fail before calling f; run_with_registry has no such pre-call failure path.

♻️ Proposed simplification
-        rx.await
-            .map_err(|_| RaindexProviderError::WorkerPanicked)?
-            .map_err(|e| match e {
-                WorkerError::RuntimeInit(e) => RaindexProviderError::RegistryRuntimeInit(e),
-                WorkerError::Api(e) => RaindexProviderError::RegistryLoad(e),
-            })
+        rx.await
+            .map_err(|_| RaindexProviderError::WorkerPanicked)?
+            .map_err(|e| match e {
+                WorkerError::RuntimeInit(e) => RaindexProviderError::RegistryRuntimeInit(e),
+            })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/raindex/config.rs` around lines 71 - 79, The match arm for
WorkerError::Api is dead because run_with_registry always sends
Ok(runtime.block_on(f(registry))), so update the error mapping in
run_with_registry to only handle WorkerError::RuntimeInit ->
RaindexProviderError::RegistryRuntimeInit (remove the WorkerError::Api(...) =>
RaindexProviderError::RegistryLoad(...) branch); locate the closure passed to
rx.await.map_err(...).map_err(|e| match e { ... }) and simplify it to directly
map the RuntimeInit variant (or use a single-arm match/if let) and remove the
unreachable WorkerError::Api reference.
🤖 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/routes/order/deploy_dca.rs`:
- Around line 14-16: The DEPOSIT_TOKEN_KEY constant is incorrectly set to
"output" so deposits go to the wrong vault; change its value to match the
input/spending token mapping by setting DEPOSIT_TOKEN_KEY to "input" (aligning
it with INPUT_TOKEN_KEY) so approvals and budget deposits for USDC go to the
correct slot; update the constant named DEPOSIT_TOKEN_KEY in the file alongside
INPUT_TOKEN_KEY and OUTPUT_TOKEN_KEY.

In `@src/routes/order/deploy_solver.rs`:
- Around line 30-31: Replace the todo!() with a proper error return using the
ApiError enum and ensure the TracingSpan is used to instrument the handler:
remove the placeholder let _ = (shared_raindex, span, request); and instead call
the async handler body (or its returned Future) with .instrument(span.0), log
the failure with tracing::error! including context, and return
Err(ApiError::InternalServerError("brief message".into())) (or a more specific
ApiError variant) instead of panicking; ensure the symbols TracingSpan, span.0,
and ApiError are used so the span is propagated and the error flows through the
ApiError response path.

---

Nitpick comments:
In `@src/raindex/config.rs`:
- Around line 71-79: The match arm for WorkerError::Api is dead because
run_with_registry always sends Ok(runtime.block_on(f(registry))), so update the
error mapping in run_with_registry to only handle WorkerError::RuntimeInit ->
RaindexProviderError::RegistryRuntimeInit (remove the WorkerError::Api(...) =>
RaindexProviderError::RegistryLoad(...) branch); locate the closure passed to
rx.await.map_err(...).map_err(|e| match e { ... }) and simplify it to directly
map the RuntimeInit variant (or use a single-arm match/if let) and remove the
unreachable WorkerError::Api reference.

In `@src/routes/order/mod.rs`:
- Around line 413-498: The duplicate mock_approve_calldata causes redundancy;
make the one in this module publicly available to the crate and remove the copy
in helpers.rs. Change the local function signature to pub(crate) fn
mock_approve_calldata(spender: Address, amount: alloy::primitives::U256) ->
Bytes (keeping the same body) inside the test_fixtures module and delete the
identical implementation in the #[cfg(test)] block of
src/routes/order/helpers.rs, then update any call sites to use the single
test_fixtures::mock_approve_calldata if necessary.
- Around line 96-148: The RealOrderDeployer's gui field is currently publicly
exposed (pub gui: DotrainOrderGui); restrict its visibility to reduce coupling
by changing its visibility to pub(super) or pub(crate) or by making it private
and adding a small constructor associated function (e.g.,
RealOrderDeployer::new(gui: DotrainOrderGui)) used by the deploy_dca.rs
construction site; update any creation sites in deploy_dca.rs to call the new
constructor or the adjusted visibility.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ccac743 and 060831b.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • Cargo.toml
  • lib/rain.orderbook
  • src/raindex/config.rs
  • src/routes/order/deploy_dca.rs
  • src/routes/order/deploy_solver.rs
  • src/routes/order/mod.rs
✅ Files skipped from review due to trivial changes (1)
  • lib/rain.orderbook
🚧 Files skipped from review as they are similar to previous changes (1)
  • Cargo.toml

Comment on lines +14 to +16
const INPUT_TOKEN_KEY: &str = "input";
const OUTPUT_TOKEN_KEY: &str = "output";
const DEPOSIT_TOKEN_KEY: &str = "output";
Copy link

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the deploy_dca.rs file
find . -name "deploy_dca.rs" -type f

Repository: ST0x-Technology/st0x.rest.api

Length of output: 105


🏁 Script executed:

# Let's look at the constant definitions and the process_deploy_dca function
cat -n src/routes/order/deploy_dca.rs | head -130

Repository: ST0x-Technology/st0x.rest.api

Length of output: 5363


🏁 Script executed:

# Search for auction-dca.rain or related strategy definitions
rg -i "auction-dca|dca" --type rs | head -40

Repository: ST0x-Technology/st0x.rest.api

Length of output: 99


🏁 Script executed:

# Let's search for where DEPOSIT_TOKEN_KEY is used
rg "DEPOSIT_TOKEN_KEY" src/

Repository: ST0x-Technology/st0x.rest.api

Length of output: 246


🏁 Script executed:

# Search for how set_deposit is called and what it does
rg "set_deposit" -A 2 -B 2 src/

Repository: ST0x-Technology/st0x.rest.api

Length of output: 2368


🏁 Script executed:

# Look at the tests in deploy_dca.rs
tail -n +130 src/routes/order/deploy_dca.rs | head -200

Repository: ST0x-Technology/st0x.rest.api

Length of output: 5068


🏁 Script executed:

# Search for any documentation or comments about auction-dca
rg -i "dca|dollar.cost" src/ --type rs -B 2 -A 2

Repository: ST0x-Technology/st0x.rest.api

Length of output: 99


🏁 Script executed:

# Look for swap route to understand the input/output pattern
find . -name "mod.rs" -path "*/routes/swap/*" -type f

Repository: ST0x-Technology/st0x.rest.api

Length of output: 97


🏁 Script executed:

# Check if there are any Rain orderbook app settings or documentation
rg "auction-dca|dca" --type toml --type md

Repository: ST0x-Technology/st0x.rest.api

Length of output: 1496


🏁 Script executed:

# Check the swap route to understand input/output patterns
cat -n src/routes/swap/mod.rs | head -200

Repository: ST0x-Technology/st0x.rest.api

Length of output: 8368


🏁 Script executed:

# Search for any comments or documentation about token semantics
rg -i "input|output" src/routes/order/deploy_dca.rs -B 1 -A 1

Repository: ST0x-Technology/st0x.rest.api

Length of output: 2157


🏁 Script executed:

# Look at the DeployDcaOrderRequest to understand the field meanings
rg -A 10 "struct DeployDcaOrderRequest" src/

Repository: ST0x-Technology/st0x.rest.api

Length of output: 788


🏁 Script executed:

# Check if there's any documentation in the types module
find . -path "*/types/*" -name "*.rs" | xargs grep -l "DeployDcaOrderRequest" 2>/dev/null

Repository: ST0x-Technology/st0x.rest.api

Length of output: 93


🏁 Script executed:

# Look at the mock fixtures to understand test setup better
rg -A 5 "mock_deployment_args" src/routes/order/

Repository: ST0x-Technology/st0x.rest.api

Length of output: 3408


🏁 Script executed:

# Look at the full mock fixtures to understand what approvals are expected
cat -n src/routes/order/mod.rs | grep -A 20 "mock_deployment_args_with_approval"

Repository: ST0x-Technology/st0x.rest.api

Length of output: 1160


🏁 Script executed:

# Search for any comments or documentation in the types/order.rs file
cat -n src/types/order.rs

Repository: ST0x-Technology/st0x.rest.api

Length of output: 8809


🏁 Script executed:

# Look for any documentation about Rain orderbook or DCA semantics
rg -i "rain|orderbook|dca" --type md --type toml | head -30

Repository: ST0x-Technology/st0x.rest.api

Length of output: 2870


🏁 Script executed:

# Check if there are any other references to USDC (MOCK_USDC) in the codebase
rg "MOCK_USDC|0x833589" src/ -B 2 -A 2

Repository: ST0x-Technology/st0x.rest.api

Length of output: 13658


Fix DEPOSIT_TOKEN_KEY to deposit budget into the correct vault.

DEPOSIT_TOKEN_KEY is set to "output", but the test approval for USDC confirms the budget should be deposited to the slot holding USDC (mapped via INPUT_TOKEN_KEY = "input"). The approval and budget amount are both for USDC, confirming it is the spending token.

Suggested fix:
-const DEPOSIT_TOKEN_KEY: &str = "output";
+const DEPOSIT_TOKEN_KEY: &str = "input";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/order/deploy_dca.rs` around lines 14 - 16, The DEPOSIT_TOKEN_KEY
constant is incorrectly set to "output" so deposits go to the wrong vault;
change its value to match the input/spending token mapping by setting
DEPOSIT_TOKEN_KEY to "input" (aligning it with INPUT_TOKEN_KEY) so approvals and
budget deposits for USDC go to the correct slot; update the constant named
DEPOSIT_TOKEN_KEY in the file alongside INPUT_TOKEN_KEY and OUTPUT_TOKEN_KEY.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is correct as-is. From the order's perspective, "output" is the token the order sends out (the spending token). The SDK's get_vaults_and_deposits maps deposits to deployment.order.outputs, so the budget must be deposited into the output vault. The naming is from the order's perspective, not the user's.

Copy link

Choose a reason for hiding this comment

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

@findolor, thank you for the clarification! You're absolutely right—I misunderstood the perspective. From the order's viewpoint, "output" is what the order sends out (the spending token), so depositing the budget into the output vault is correct. I appreciate you explaining the get_vaults_and_deposits mapping to deployment.order.outputs.


✏️ Learnings added
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 36
File: src/routes/order/deploy_dca.rs:14-16
Timestamp: 2026-02-24T10:02:37.086Z
Learning: In the Rain orderbook architecture, vault naming uses the order's perspective, not the user's perspective. From the order's viewpoint: "input" vaults hold tokens the order receives (asks for), and "output" vaults hold tokens the order sends out (offers). For example, in a DCA order where a user sells USDC to buy ETH, the order's output vault holds USDC (what it sends out). The SDK's `get_vaults_and_deposits` maps deposits to `deployment.order.outputs`, so budget deposits must go to the output vault even though from a user perspective it might seem like an "input."

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 18
File: src/fairings/rate_limiter.rs:158-203
Timestamp: 2026-02-13T09:37:37.357Z
Learning: When multiple guards or layers write to the same cached rate-limit information, document and enforce the execution order so that the client-visible headers reflect the intended quota (global vs per-key). In this repo, rate-limiter.rs and auth-related guards intentionally write to the same CachedRateLimitInfo entry; ensure the per-key quota is what’s surfaced to clients and that the order is explicit in route signatures. Include tests that verify the final headers reflect the intended quota and that writes are intentional and auditable.

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 38
File: src/routes/swap/mod.rs:115-129
Timestamp: 2026-02-17T09:35:57.431Z
Learning: In Rust server code for swap endpoints, if the response contains a non-empty approvals array, the behavior uses a two-phase flow: first the client sends an approval transaction, then queries the endpoint again to obtain the actual swap calldata. In the approval response, set to = approval.spender() to signal the eventual target, and set data = Bytes::new() (empty) to indicate 'no transaction yet.' Prefer this approach over using Address::ZERO for clarity. This pattern is specific to the swap flow in src/routes/swap/mod.rs and should be followed in similar swap-related endpoints with analogous approval semantics.

Comment on lines +30 to +31
let _ = (shared_raindex, span, request);
todo!()
Copy link

@coderabbitai coderabbitai bot Feb 24, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

todo!() panics instead of returning a clean ApiError; TracingSpan is not instrumented.

Two coding guideline violations here:

  1. todo!() causes a runtime panic instead of returning through the ApiError enum. While Rocket's async runtime will catch the panic and emit a 500, it won't produce a structured ApiErrorResponse body, bypasses all logging, and violates the guideline that errors must go through ApiError.

  2. Missing .instrument(span.0) — the span is immediately dropped via let _ = (...) and never used, violating the guideline that all async route handlers must use TracingSpan and .instrument(span.0) for span propagation.

🛡️ Proposed fix
-    let _ = (shared_raindex, span, request);
-    todo!()
+    let _ = (shared_raindex, request);
+    async move {
+        tracing::warn!("POST /v1/order/solver is not yet implemented");
+        Err(ApiError::Internal("solver order deployment is not yet implemented".into()))
+    }
+    .instrument(span.0)
+    .await

As per coding guidelines: "Every route handler must log appropriately using tracing", "All async route handlers must use TracingSpan and .instrument(span.0) for span propagation", and "All API errors must go through the ApiError enum, never return raw status codes."

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

In `@src/routes/order/deploy_solver.rs` around lines 30 - 31, Replace the todo!()
with a proper error return using the ApiError enum and ensure the TracingSpan is
used to instrument the handler: remove the placeholder let _ = (shared_raindex,
span, request); and instead call the async handler body (or its returned Future)
with .instrument(span.0), log the failure with tracing::error! including
context, and return Err(ApiError::InternalServerError("brief message".into()))
(or a more specific ApiError variant) instead of panicking; ensure the symbols
TracingSpan, span.0, and ApiError are used so the span is propagated and the
error flows through the ApiError response path.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Intentional. This endpoint is explicitly stubbed pending the solver strategy spec, as noted in the PR description. The entire function body will be replaced when the solver is implemented.

Copy link

Choose a reason for hiding this comment

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

@findolor, understood — I see this is explicitly called out in the PR description as a temporary stub. Thanks for clarifying!


🧠 Learnings used
Learnt from: CR
Repo: ST0x-Technology/st0x.rest.api PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-19T09:28:42.183Z
Learning: Applies to **/*.rs : All async route handlers must use `TracingSpan` and `.instrument(span.0)` for span propagation

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 18
File: src/fairings/rate_limiter.rs:158-203
Timestamp: 2026-02-13T09:37:37.357Z
Learning: When multiple guards or layers write to the same cached rate-limit information, document and enforce the execution order so that the client-visible headers reflect the intended quota (global vs per-key). In this repo, rate-limiter.rs and auth-related guards intentionally write to the same CachedRateLimitInfo entry; ensure the per-key quota is what’s surfaced to clients and that the order is explicit in route signatures. Include tests that verify the final headers reflect the intended quota and that writes are intentional and auditable.

Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 38
File: src/routes/swap/mod.rs:115-129
Timestamp: 2026-02-17T09:35:57.431Z
Learning: In Rust server code for swap endpoints, if the response contains a non-empty approvals array, the behavior uses a two-phase flow: first the client sends an approval transaction, then queries the endpoint again to obtain the actual swap calldata. In the approval response, set to = approval.spender() to signal the eventual target, and set data = Bytes::new() (empty) to indicate 'no transaction yet.' Prefer this approach over using Address::ZERO for clarity. This pattern is specific to the swap flow in src/routes/swap/mod.rs and should be followed in similar swap-related endpoints with analogous approval semantics.

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.

Implement POST /v1/order/dca

1 participant