Implement POST /v1/order/dca endpoint, stub solver#36
Conversation
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.
📝 WalkthroughWalkthroughImplements 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
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 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 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
🧹 Nitpick comments (5)
src/raindex/config.rs (1)
45-76: UnreachableWorkerError::Apiarm in error mapping.On line 67, the closure result is always wrapped in
Ok(...), soWorkerError::Apiis never produced insiderun_with_registry. The match arm on line 74 is dead code. Unlikerun_with_clientwhereget_raindex_clientcan fail withWorkerError::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
Resultif 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 toorderbook_addressinstead of decoded from calldata.
decode_approval_amountalready decodes the fullapproveCall(which includesspender), but the decoded spender is discarded. TheApproval.spenderis then set toargs.orderbook_addresson 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
spenderandamountfrom 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_KEYduplicatesOUTPUT_TOKEN_KEY— add a clarifying comment.Both
OUTPUT_TOKEN_KEYandDEPOSIT_TOKEN_KEYresolve 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 betweenpost_order_dcaandpost_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.
MockOrderDeployerreturns the same canned result for every call toset_field_value,set_select_token, etc., regardless of arguments. This means tests can't verify thatprocess_deploy_dcapasses 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.
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.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
src/routes/order/mod.rs (2)
413-498:mock_approve_calldatais duplicated — consider consolidating intotest_fixtures.
mock_approve_calldata(lines 416–418) is defined intest_fixtureshere, but a copy also exists in the#[cfg(test)]block ofsrc/routes/order/helpers.rs(snippet line 50–52). Both have identical logic. Sincetest_fixturesis alreadypub(crate), making the version herepub(crate) fn mock_approve_calldata(...)and removing the duplicate fromhelpers.rseliminates 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:RealOrderDeployerimplementation and error mapping look correct; consider narrowingguifield visibility.All method delegations include
tracing::error!on failures and map to appropriateApiErrorvariants (BadRequestfor user-correctable inputs,Internalfor deployment args). The only minor point:pub gui: DotrainOrderGui(line 93) exposes the field publicly within the crate. Since the only construction site indeploy_dca.rsuses struct literal syntax, apub(super)orpub(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::Apiarm in the error mapper is unreachable dead code.
run_with_registryalways sendsOk(runtime.block_on(f(registry)))(line 71); it never constructsWorkerError::Api(...). TheWorkerError::Api(e) => RaindexProviderError::RegistryLoad(e)arm at line 78 is therefore never reached.run_with_clientpropagates API errors through that variant because it explicitly callsget_raindex_client()and may fail before callingf;run_with_registryhas 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
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (6)
Cargo.tomllib/rain.orderbooksrc/raindex/config.rssrc/routes/order/deploy_dca.rssrc/routes/order/deploy_solver.rssrc/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
| const INPUT_TOKEN_KEY: &str = "input"; | ||
| const OUTPUT_TOKEN_KEY: &str = "output"; | ||
| const DEPOSIT_TOKEN_KEY: &str = "output"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, let's find and examine the deploy_dca.rs file
find . -name "deploy_dca.rs" -type fRepository: 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 -130Repository: 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 -40Repository: 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 -200Repository: 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 2Repository: 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 fRepository: 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 mdRepository: 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 -200Repository: 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 1Repository: 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/nullRepository: 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.rsRepository: 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 -30Repository: 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 2Repository: 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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.
| let _ = (shared_raindex, span, request); | ||
| todo!() |
There was a problem hiding this comment.
todo!() panics instead of returning a clean ApiError; TracingSpan is not instrumented.
Two coding guideline violations here:
-
todo!()causes a runtime panic instead of returning through theApiErrorenum. While Rocket's async runtime will catch the panic and emit a 500, it won't produce a structuredApiErrorResponsebody, bypasses all logging, and violates the guideline that errors must go throughApiError. -
Missing
.instrument(span.0)— the span is immediately dropped vialet _ = (...)and never used, violating the guideline that all async route handlers must useTracingSpanand.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)
+ .awaitAs 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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
@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.
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— AcceptsDeployDcaOrderRequest(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 theauction-dca.rainstrategy (input,output,amount-per-epoch,time-per-amount-epoch,initial-io,baseline).POST /v1/order/solver— Route registered and visible in OpenAPI but handler istodo!()until the solver strategy spec is known.OrderDeployertrait — AbstractsDotrainOrderGuimethods (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.RealOrderDeployerwraps the concrete GUI;MockOrderDeployeris used in tests.helpers.rs— Sharedmap_deployment_to_responseconvertsDeploymentTransactionArgstoDeployOrderResponse, decoding approval calldata amounts.RaindexProvider::run_with_registry— New method to execute closures with aDotrainRegistryon a dedicated thread (registry is!Send).Clonederive toApiErrorto support mock trait implementations.Checks
By submitting this for review, I'm confirming I've done the following:
fix #22
Summary by CodeRabbit
New Features
Chores