Skip to content

Conversation

@nahimterrazas
Copy link
Collaborator

@nahimterrazas nahimterrazas commented Feb 3, 2026

Summary by CodeRabbit

  • New Features

    • AWS KMS signing backend and a unified signer abstraction; account creation is now asynchronous and services consume signer objects.
  • Documentation

    • Added comprehensive AWS KMS Signing guide; SOLVER_PRIVATE_KEY noted as optional and docs updated for multiple account backends.
  • Tests

    • Many tests and helpers migrated to async to support async account creation and signer flows.
  • Chores

    • Rust toolchain bumped to 1.88.0 and KMS-related dependencies added.
  • Style

    • Standardized error and log message formatting across the codebase.

- Added  package to support AWS KMS signing.
- Updated  and  to include new dependencies.
- Implemented  for KMS-backed signing operations.
- Enhanced  to support unified signing through .
- Introduced new features for KMS integration in the  crate.
- Updated delivery and builder logic to utilize the new signer abstraction.
@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Adds a feature-gated AWS KMS account backend and unified AccountSigner; converts account factories to async futures; replaces raw private-key flows with signer objects across delivery/core layers and tests; bumps Rust toolchain and workspace deps; updates docs and config merging to support account overrides.

Changes

Cohort / File(s) Summary
Workspace / Toolchain
Cargo.toml, rust-toolchain.toml, .github/actions/prepare/action.yaml
Bump Rust toolchain to 1.88.0 and workspace rust-version; add AWS KMS workspace dependencies (alloy-signer-aws, aws-config, aws-sdk-kms).
Documentation
README.md
Add AWS KMS Signing docs, TOC entry, IAM/prereqs, build/run and LocalStack notes; mark SOLVER_PRIVATE_KEY optional when using KMS.
Account: signer abstraction & API
crates/solver-account/src/signer.rs, crates/solver-account/src/lib.rs
Introduce AccountSigner enum (Local + feature-gated Kms), re-export it; add signer() to AccountInterface; deprecate direct get_private_key; make account factories async (AccountFactoryFuture).
Account: KMS implementation
crates/solver-account/src/implementations/kms.rs, crates/solver-account/Cargo.toml
Add feature-gated kms implementation (KmsWallet, schema, async constructor) wiring AWS SDK and AwsSigner; kms feature enables optional AWS deps.
Account: local changes & tests
crates/solver-account/src/implementations/local.rs
Return AccountSigner::Local from signer(), make create_account async, update error formatting and convert tests to async including signer-related tests.
Delivery: API & implementations
crates/solver-delivery/src/lib.rs, crates/solver-delivery/src/implementations/evm/alloy.rs
Change DeliveryFactory and AlloyDelivery to accept solver_account::AccountSigner for default and per-network signers (replacing SecretString/PrivateKeySigner); update call sites and tests.
Core builder & wiring
crates/solver-core/src/builder/mod.rs, crates/solver-core/src/engine/*
Make SolverBuilder account factory param async (higher-ranked for<'a> -> boxed Future); obtain and propagate AccountSigner to delivery; await account creation; update tests to async.
Service registry & types
crates/solver-service/src/factory_registry.rs, crates/solver-service/Cargo.toml, crates/solver-service/src/**
Update AccountFactory/DeliveryFactory type aliases to async/future-based signatures and to use solver_account::AccountSigner; add kms feature enabling solver-account/kms; adjust tests/helpers to async.
Config & operator account round-trip
crates/solver-service/src/config_merge.rs, crates/solver-types/src/operator_config.rs, crates/solver-types/src/seed_overrides.rs
Add account override support to seed/operator config types, JSON↔TOML conversion helpers, and round-trip preservation of non-local account configs; OperatorConfig gains optional account field.
Many crates: formatting & tests
crates/** (multiple files)
Widespread stylistic migration from positional format! args to named interpolation ({e}, {var}) across many files; many tests converted to async to support async factories.
CI / dev-deps / testing
crates/solver-core/Cargo.toml, others
Adjust dev-dependencies and testing features (e.g., alloy-signer-local) to support async tests and new signer types.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SolverBuilder
    participant AccountFactory
    participant AccountService
    participant AccountInterface
    participant AccountSigner
    participant KmsWallet
    participant AwsSigner
    participant AWS_KMS

    Client->>SolverBuilder: build(config)
    SolverBuilder->>AccountFactory: invoke factory(config)
    AccountFactory-->>SolverBuilder: await -> returns AccountInterface
    SolverBuilder->>AccountService: create with AccountInterface
    AccountService->>AccountInterface: signer()
    AccountInterface-->>AccountService: AccountSigner (Local or Kms)

    alt KMS selected
        AccountService->>KmsWallet: KmsWallet::new(key_id, region, endpoint)
        KmsWallet->>AwsSigner: construct AwsSigner (aws-config)
        AwsSigner->>AWS_KMS: sign request
        AWS_KMS-->>AwsSigner: signature
        AwsSigner-->>KmsWallet: signature
        KmsWallet-->>AccountService: AccountSigner::Kms
    else Local selected
        AccountService-->>AccountService: AccountSigner::Local (wrap PrivateKeySigner)
    end

    Client->>AccountSigner: sign_hash(hash)
    alt KMS
        AccountSigner->>AwsSigner: sign_hash -> AWS_KMS flow
    else Local
        AccountSigner->>PrivateKeySigner: sign_hash locally
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • shahnami
  • pepebndc
  • NicoMolinaOZ

Poem

🐰 I nibble keys beneath the moonlight,

Async hops and signers spring to light.
KMS or local, both take flight,
Docs and tests all in sight.
A rabbit cheers: secure signing, right!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is entirely empty; no description content was provided by the author. Add a comprehensive description following the repository template with a Summary section, Testing Process details, and completion of the provided checklist items.
Title check ❓ Inconclusive The title 'Kms implementation' is vague and generic, using non-descriptive terminology that doesn't convey meaningful details about the actual changes. Use a more specific title that describes the main change, such as 'Add AWS KMS signer support for account management' or 'Implement KMS-backed wallet implementation'.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kms-implementation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

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

🤖 Fix all issues with AI agents
In `@Cargo.toml`:
- Around line 79-81: Update the AWS SDK crate versions in Cargo.toml to patched,
compatible releases: bump aws-config to at least 1.8.12 (or use aws-config = {
version = "1", features = ["behavior-version-latest"] }) and aws-sdk-kms to
1.98.0+ so the KMS client is not vulnerable; ensure both crates come from the
same AWS SDK Rust release set (verify pairing on the AWS SDK releases page) and
update the lockfile by running cargo update after changing the dependency
versions.

In `@crates/solver-account/src/implementations/kms.rs`:
- Around line 150-167: The code uses tokio::task::block_in_place when
tokio::runtime::Handle::try_current() returns Ok, which will panic if that
handle is a current-thread runtime; change the strategy to avoid block_in_place:
detect the runtime flavor returned by try_current() and if it is (or might be) a
current-thread runtime, use handle.spawn_blocking (or spawn a blocking task that
builds a temporary current-thread runtime and rt.block_on(KmsWallet::new(...)))
to run the async KmsWallet::new call safely; keep the existing fallback that
builds a new current-thread runtime for the Err case. Update the logic around
try_current(), tokio::task::block_in_place, and KmsWallet::new (or alternatively
make create_account async) so no code path calls block_in_place on a
current-thread runtime.

In `@README.md`:
- Around line 423-428: Update the "Performance Considerations" section: replace
the incorrect "~5,500 cryptographic requests/second" claim with accurate
per-Region, per-account quotas from AWS KMS — state that Sign (RSA/ECC) is
limited to 1,000 requests/second (shared across Sign, Verify, and related
operations) and GetPublicKey is 2,000 requests/second, and explicitly note these
limits are per AWS account per Region and vary by operation type; leave the
latency estimate unchanged.
🧹 Nitpick comments (4)
README.md (1)

403-422: Consider documenting tool dependencies for the AWS CLI address derivation.

The wallet address derivation section is well-structured with both automated and manual approaches. The AWS CLI command pipeline is technically sound for deriving Ethereum addresses from KMS public keys.

However, the command relies on tools that may not be readily available (openssl, keccak-256sum). Consider adding a brief note about these dependencies, though the "simpler approach" recommendation (running the solver once) already provides a practical alternative.

📝 Optional: Add tool dependency note
 You can also query the address directly using the AWS CLI:

 ```bash
+# Note: Requires openssl and keccak-256sum tools
 # Get public key from KMS and derive Ethereum address
 aws kms get-public-key --key-id "your-key-id" --output text --query 'PublicKey' | \
crates/solver-account/Cargo.toml (1)

19-19: Consider removing the duplicate tokio dependency.

tokio appears in both [dependencies] (line 19) and [dev-dependencies] (line 31). If tokio is only needed for tests, it should remain solely in [dev-dependencies]. If it's required at runtime (e.g., for the KMS async operations), then the [dev-dependencies] entry is redundant and can be removed.

Also applies to: 30-31

crates/solver-account/src/implementations/kms.rs (2)

13-16: Unused import SecretString.

SecretString is imported but never used in this module.

🧹 Proposed fix
 use solver_types::{
-	Address, ConfigSchema, Field, FieldType, Schema, SecretString, Signature, Transaction,
+	Address, ConfigSchema, Field, FieldType, Schema, Signature, Transaction,
 	ValidationError,
 };

125-127: The default get_private_key() implementation already provides a descriptive panic message.

The trait default implementation at lib.rs:85-87 panics with "get_private_key() not supported - use signer() instead", and the method is marked #[deprecated] in the public wrapper. No code paths unconditionally call this without type awareness—usage is confined to LocalWallet tests.

However, the design could be improved: returning a Result<SecretString, AccountError> instead of panicking would provide better error handling than relying on panic even with a descriptive message. Alternatively, the method could remain deprecated as-is if panic-on-misuse is the intended design.

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

🤖 Fix all issues with AI agents
In `@Cargo.toml`:
- Line 22: CI uses an older rust toolchain than the crate MSRV: update the
prepare action reference dtolnay/rust-toolchain@1.86.0 in
.github/actions/prepare/action.yaml to dtolnay/rust-toolchain@1.88.0 so it
matches Cargo.toml's rust-version = "1.88.0" and rust-toolchain.toml; ensure the
action reference string is changed and commit the update so all toolchain
specifications align.

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

🤖 Fix all issues with AI agents
In @.github/actions/prepare/action.yaml:
- Line 17: Update the stale inline comment next to the GitHub Action pin so it
reflects the actual toolchain version: change the comment after uses:
dtolnay/rust-toolchain@1.88.0 to mention v1.88.0 (replacing v1.86.0). Ensure the
comment text matches the pinned tag in the action declaration to avoid confusion
when using the dtolnay/rust-toolchain@1.88.0 reference.

- Changed  functions in local and KMS implementations to return async futures.
- Updated related tests to use async/await syntax for account creation.
- Modified the  type alias to reflect the new async signature.
- Adjusted the  and other components to accommodate the async account creation process.
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: 1

🤖 Fix all issues with AI agents
In `@crates/solver-account/src/lib.rs`:
- Around line 203-288: Tests in AccountService (e.g., test_account_service_new,
test_account_service_get_address, test_account_service_sign,
test_account_service_get_private_key, test_account_service_signer) include a
hardcoded private key literal which triggers secret scanners; replace this by
centralizing a test-only key provider (e.g., a helper function like
test_helpers::test_private_key or implementations::local::test_key()) and have
tests obtain the key by assembling it from non-secret parts or reading from a
test-only env/config variable, then pass that config into
create_account(&config).update all tests to use that helper instead of the
inline literal or explicitly allowlist the single known test key in CI instead.

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

Caution

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

⚠️ Outside diff range comments (6)
crates/solver-service/src/apis/quote/validation.rs (1)

412-425: ⚠️ Potential issue | 🟠 Major

Don’t swallow invalid amounts when falling back to request values.

amount_as_u256() parse errors are discarded via ok().flatten(), turning invalid amounts into 0 instead of rejecting the request. This can silently produce incorrect quotes. Propagate the parse error instead.

✅ Suggested fix (propagate parsing errors)
-			let amount = cost_context
-				.swap_amounts
-				.get(asset_addr)
-				.map(|info| info.amount)
-				.unwrap_or_else(|| {
-					// Fallback to request amount if not in cost context
-					asset_info
-						.amount_as_u256()
-						.map_err(|e| QuoteError::InvalidRequest(format!("Invalid amount: {e}")))
-						.ok()
-						.flatten()
-						.unwrap_or_default()
-				});
+			let amount = if let Some(info) = cost_context.swap_amounts.get(asset_addr) {
+				info.amount
+			} else {
+				// Fallback to request amount if not in cost context
+				asset_info
+					.amount_as_u256()
+					.map_err(|e| QuoteError::InvalidRequest(format!("Invalid amount: {e}")))?
+					.unwrap_or_default()
+			};
-			let amount = cost_context
-				.swap_amounts
-				.get(asset_addr)
-				.map(|info| info.amount)
-				.unwrap_or_else(|| {
-					// Fallback to request amount if not in cost context
-					asset_info
-						.amount_as_u256()
-						.map_err(|e| QuoteError::InvalidRequest(format!("Invalid amount: {e}")))
-						.ok()
-						.flatten()
-						.unwrap_or_default()
-				});
+			let amount = if let Some(info) = cost_context.swap_amounts.get(asset_addr) {
+				info.amount
+			} else {
+				// Fallback to request amount if not in cost context
+				asset_info
+					.amount_as_u256()
+					.map_err(|e| QuoteError::InvalidRequest(format!("Invalid amount: {e}")))?
+					.unwrap_or_default()
+			};

Also applies to: 475-488

crates/solver-demo/src/operations/env/deploy.rs (1)

228-234: ⚠️ Potential issue | 🟡 Minor

Silent failure on ABI encoding error.

unwrap_or_default() returns empty bytes if encoding fails, which would cause contract deployment to proceed with missing constructor arguments - leading to hard-to-debug failures or incorrect contract initialization.

🛡️ Proposed fix to propagate encoding errors
 fn encode_constructor_args(&self, constructor_args: Vec<DynSolValue>) -> Result<Bytes> {
     let tuple_value = DynSolValue::Tuple(constructor_args);
-    let encoded_args = tuple_value.abi_encode_sequence().unwrap_or_default();
-    Bytes::from(encoded_args)
+    let encoded_args = tuple_value
+        .abi_encode_sequence()
+        .ok_or_else(|| Error::InvalidConfig("Failed to ABI encode constructor arguments".to_string()))?;
+    Ok(Bytes::from(encoded_args))
 }

Note: This would require updating all call sites to handle the Result.

crates/solver-core/src/handlers/transaction.rs (1)

600-606: ⚠️ Potential issue | 🟠 Major

Fix assertion to check for correct status transition: OrderStatus::Executed

The panic message formatting is correct. However, line 605 has a significant bug: the assertion checks OrderStatus::Executing but should check OrderStatus::Executed. The handler at line 198 explicitly transitions the status to Executed via transition_order_status(&order_id, OrderStatus::Executed), and the comment at line 603 confirms this intent ("updated to Executed"). The test only passes because the mock returns a hard-coded Executing status that bypasses the actual state transition logic.

Change line 605 to:

assert_eq!(updated_order.status, OrderStatus::Executed);
crates/solver-settlement/src/implementations/direct.rs (1)

169-175: ⚠️ Potential issue | 🟠 Major

Use a stable hash for deterministic oracle selection.

The code explicitly intends deterministic oracle selection (as stated in the comments), but DefaultHasher is not guaranteed to be stable across Rust versions. Per official Rust documentation, its algorithm is unspecified and must not be relied upon across releases. This means different Rust toolchains could pick different oracles for the same order, breaking the stated determinism guarantee in a distributed system.

The codebase already has sha3 as a dependency, so the fix is straightforward:

🔧 Suggested change
-		let order_id_bytes = order.id.as_bytes();
-		let mut hasher = std::collections::hash_map::DefaultHasher::new();
-		std::hash::Hasher::write(&mut hasher, order_id_bytes);
-		let selection_context = std::hash::Hasher::finish(&hasher);
+		use sha3::{Digest, Keccak256};
+		let mut hasher = Keccak256::new();
+		hasher.update(order.id.as_bytes());
+		let digest = hasher.finalize();
+		let selection_context = u64::from_be_bytes(digest[0..8].try_into().unwrap());
crates/solver-types/src/utils/conversion.rs (1)

145-170: ⚠️ Potential issue | 🟠 Major

Guard against truncating non-address high bytes after padding.

With the new 41–63 length branch, values larger than 20 bytes are now accepted, padded to 32 bytes, and then silently truncated to the last 20 bytes. That can mis-parse inputs where the upper 12 bytes are non-zero (i.e., not a valid left‑padded address). Consider validating/normalizing the 32‑byte form before slicing.

Suggested fix
-				32 => {
-					// bytes32 representation - extract last 20 bytes as address
-					let mut addr_bytes = vec![0u8; 20];
-					addr_bytes.copy_from_slice(&bytes[12..32]);
-					Ok(Address(addr_bytes))
-				},
+				32 => {
+					// Normalize right-padded forms and ensure left-padding is zeroed
+					let bytes32: [u8; 32] =
+						bytes.try_into().map_err(|_| "Failed to convert to 32-byte array")?;
+					let normalized = normalize_bytes32_address(bytes32);
+					if normalized[..12].iter().any(|&b| b != 0) {
+						return Err("Invalid bytes32 address: non-zero left padding".into());
+					}
+					let mut addr_bytes = vec![0u8; 20];
+					addr_bytes.copy_from_slice(&normalized[12..32]);
+					Ok(Address(addr_bytes))
+				},
crates/solver-delivery/src/implementations/evm/alloy.rs (1)

576-612: ⚠️ Potential issue | 🟠 Major

Avoid block_in_place on current-thread runtimes.

tokio::task::block_in_place panics under a current-thread runtime, which is the default for #[tokio::test]. Moving this async initialization into a context where it's safe is necessary.

Consider checking the runtime flavor and handling current-thread runtimes separately:

🔧 Suggested runtime-safe approach
-	let delivery = tokio::task::block_in_place(|| {
-		tokio::runtime::Handle::current().block_on(async {
-			AlloyDelivery::new(network_ids, networks, network_signers, default_signer).await
-		})
-	})?;
+	let delivery = match tokio::runtime::Handle::try_current() {
+		Ok(handle) => {
+			if handle.runtime_flavor() == tokio::runtime::RuntimeFlavor::MultiThread {
+				tokio::task::block_in_place(|| {
+					handle.block_on(async {
+						AlloyDelivery::new(network_ids, networks, network_signers, default_signer).await
+					})
+				})?
+			} else {
+				// Current-thread runtime: spawn blocking task on a separate thread
+				std::thread::spawn(move || {
+					tokio::runtime::Runtime::new()
+						.and_then(|rt| {
+							Ok(rt.block_on(async {
+								AlloyDelivery::new(network_ids, networks, network_signers, default_signer).await
+							}))
+						})
+				})
+				.join()
+				.expect("delivery init thread panicked")?
+			}
+		}
+		Err(_) => {
+			let rt = tokio::runtime::Builder::new_current_thread()
+				.enable_all()
+				.build()
+				.map_err(|e| DeliveryError::Network(format!("Failed to build runtime: {e}")))?;
+			rt.block_on(async {
+				AlloyDelivery::new(network_ids, networks, network_signers, default_signer).await
+			})?
+		}
+	};
🤖 Fix all issues with AI agents
In `@crates/solver-core/src/engine/mod.rs`:
- Around line 483-488: The expression EngineError::Service(...) is constructed
and immediately discarded; update the error handling in the block that also
calls tracing::error! for the failed order_id retrieval: either remove the dead
EngineError::Service(...) expression entirely if you only want to log the
failure, or use it (e.g., return Err(EngineError::Service(...)) or propagate it
to the caller) instead of continuing; ensure the chosen fix touches the Err(e)
arm that currently logs with tracing::error!, references order_id and e, and
removes the unused EngineError::Service construction.

In `@crates/solver-delivery/src/implementations/evm/alloy.rs`:
- Around line 645-650: The hardcoded private key literal in create_test_signer
(and the similar literal at the other location referenced) should be moved out
of inline code into a centralized, test-only source so secret scanners won't
flag it: create a test utilities module or const guarded by cfg(test) (e.g.,
TEST_PRIVATE_KEY) or read from an explicit test-only env var (TEST_PRIVATE_KEY)
with a test-only fallback, then have create_test_signer and the other usage
reference that symbol instead of embedding the hex string; ensure the
centralized symbol is only compiled for tests (cfg(test)) or documented as
allowlisted for CI.

In `@crates/solver-types/src/standards/eip7683.rs`:
- Around line 912-918: In from_batch_compact, parsing for token_bytes and
recipient_bytes uses unwrap_or([0u8;32]) while oracle_bytes and settler_bytes
propagate errors via map_err + ?; this is inconsistent with from_permit2 and
causes silent defaults for some invalid hex inputs—choose one behavior and apply
it to all four fields: either make token_bytes and recipient_bytes propagate
parse errors like oracle_bytes/settler_bytes by replacing unwrap_or with
map_err(...)? or make oracle_bytes/settler_bytes use unwrap_or([0u8;32]) to
match from_permit2; update parse_bytes32_from_hex usages in from_batch_compact
accordingly so all four fields (token_bytes, recipient_bytes, oracle_bytes,
settler_bytes) follow the same error-handling strategy.
🧹 Nitpick comments (6)
README.md (2)

407-409: Add language specifier to fenced code block.

The log output example is missing a language specifier, which triggers a markdownlint warning (MD040).

📝 Suggested fix
-```
+```text
 INFO solver_core::builder: Solver address initialized component="account" address=0x1234...abcd
+```

413-418: Complex shell pipeline for address derivation may be fragile.

The multi-step pipeline to derive an Ethereum address from KMS public key uses several tools (openssl, keccak-256sum) that may not be available on all systems. The suggestion to "run the solver once" (line 421) is more reliable.

Consider simplifying this section to just recommend running the solver, or add a note about required dependencies (openssl, a keccak-256 utility).

crates/solver-service/src/apis/quote/generation.rs (1)

2861-2861: Debug print retained in test.

The println! statement is useful for debugging test failures but will produce output during cargo test. Consider using eprintln! or removing it if the assertion message on line 2863-2865 provides sufficient context for failure diagnosis.

crates/solver-service/src/server.rs (1)

911-929: Test isolation differs from production hot-reload behavior.

The test helper creates a new Arc<RwLock<Config>> (line 914) rather than using solver.dynamic_config().clone() as the production code does (line 72). This means config updates to the solver won't propagate to AppState.config in tests.

This is fine for the current tests which don't verify hot-reload behavior, but if future tests need to validate that admin API updates propagate to the solver, this would need adjustment.

♻️ Optional: Align with production behavior
 async fn build_test_app_state(discovery_url: Option<String>) -> AppState {
     let solver = build_test_solver_engine().await;
-    let config = solver.config().clone();
-    let dynamic_config = Arc::new(RwLock::new(config));
+    let dynamic_config = solver.dynamic_config().clone();

     let http_client = Client::builder()
crates/solver-demo/src/operations/init/mod.rs (2)

604-641: Use the placeholder prefix constants for lookups to avoid drift.
Right now the lookup strings are hardcoded; if the prefix constants ever change, these lookups will break.

♻️ Suggested change
-        let oracle_addr = placeholders
-            .get(&format!("ORACLE_PLACEHOLDER_INPUT_{chain_id}"))
+        let oracle_addr = placeholders
+            .get(&format!("{ORACLE_PLACEHOLDER_INPUT_PREFIX}{chain_id}"))
             .ok_or_else(|| {
-                Error::InvalidConfig(format!("Missing ORACLE_PLACEHOLDER_INPUT_{chain_id}"))
+                Error::InvalidConfig(format!(
+                    "Missing {ORACLE_PLACEHOLDER_INPUT_PREFIX}{chain_id}"
+                ))
             })?;
@@
-        let oracle_addr = placeholders
-            .get(&format!("ORACLE_PLACEHOLDER_OUTPUT_{chain_id}"))
+        let oracle_addr = placeholders
+            .get(&format!("{ORACLE_PLACEHOLDER_OUTPUT_PREFIX}{chain_id}"))
             .ok_or_else(|| {
-                Error::InvalidConfig(format!("Missing ORACLE_PLACEHOLDER_OUTPUT_{chain_id}"))
+                Error::InvalidConfig(format!(
+                    "Missing {ORACLE_PLACEHOLDER_OUTPUT_PREFIX}{chain_id}"
+                ))
             })?;

Also applies to: 656-657


676-749: Prefer prefix constants for placeholder key lookups here too.
Keeps generation and lookup in sync and prevents mismatches if constants change.

♻️ Suggested change (pattern)
-        let input_settler = placeholders
-            .get(&format!("PLACEHOLDER_INPUT_SETTLER_{chain_id}"))
+        let input_settler = placeholders
+            .get(&format!("{PLACEHOLDER_INPUT_SETTLER_PREFIX}{chain_id}"))
             .ok_or_else(|| {
-                Error::InvalidConfig(format!("Missing PLACEHOLDER_INPUT_SETTLER_{chain_id}"))
+                Error::InvalidConfig(format!(
+                    "Missing {PLACEHOLDER_INPUT_SETTLER_PREFIX}{chain_id}"
+                ))
             })?;
@@
-        let token_a = placeholders
-            .get(&format!("PLACEHOLDER_TOKEN_A_{chain_id}"))
+        let token_a = placeholders
+            .get(&format!("{PLACEHOLDER_TOKEN_A_PREFIX}{chain_id}"))
             .ok_or_else(|| {
-                Error::InvalidConfig(format!("Missing PLACEHOLDER_TOKEN_A_{chain_id}"))
+                Error::InvalidConfig(format!(
+                    "Missing {PLACEHOLDER_TOKEN_A_PREFIX}{chain_id}"
+                ))
             })?;

Apply the same pattern for the other placeholder lookups in this function.

Comment on lines +645 to +650
fn create_test_signer() -> AccountSigner {
let private_key_signer: PrivateKeySigner =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
.parse()
.unwrap();
AccountSigner::Local(private_key_signer)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded test private keys can trigger secret scanners.

Even test-only literals are frequently flagged (and can fail CI). Consider centralizing and splitting the literals (or allowlisting explicitly).

🛡️ Suggested test-key centralization/splitting
 #[cfg(test)]
 mod tests {
 	use super::*;
 	use alloy_signer_local::PrivateKeySigner;
 	use solver_types::utils::tests::builders::{NetworkConfigBuilder, NetworksConfigBuilder};
 	use std::collections::HashMap;
+
+	const TEST_DEFAULT_PRIVATE_KEY: &str = concat!(
+		"0xac0974bec39a17e3",
+		"6ba4a6b4d238ff94",
+		"4bacb478cbed5efc",
+		"ae784d7bf4f2ff80",
+	);
+	const TEST_NETWORK_PRIVATE_KEY: &str = concat!(
+		"0x59c6995e998f97a5",
+		"a0044966f0945389",
+		"dc9e86dae88c7a84",
+		"12f4603b6b78690d",
+	);
 
 	fn create_test_networks() -> NetworksConfig {
 		NetworksConfigBuilder::new()
 			.add_network(1, NetworkConfigBuilder::new().build())
 			.add_network(137, NetworkConfigBuilder::new().build())
 			.build()
 	}
 
 	fn create_test_signer() -> AccountSigner {
-		let private_key_signer: PrivateKeySigner =
-			"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
-				.parse()
-				.unwrap();
+		let private_key_signer: PrivateKeySigner = TEST_DEFAULT_PRIVATE_KEY.parse().unwrap();
 		AccountSigner::Local(private_key_signer)
 	}
@@
 	async fn test_network_specific_signers() {
@@
-		let network_signer_key: PrivateKeySigner =
-			"0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
-				.parse()
-				.unwrap();
+		let network_signer_key: PrivateKeySigner = TEST_NETWORK_PRIVATE_KEY.parse().unwrap();
 		let network_signer = AccountSigner::Local(network_signer_key);

Also applies to: 875-879

🤖 Prompt for AI Agents
In `@crates/solver-delivery/src/implementations/evm/alloy.rs` around lines 645 -
650, The hardcoded private key literal in create_test_signer (and the similar
literal at the other location referenced) should be moved out of inline code
into a centralized, test-only source so secret scanners won't flag it: create a
test utilities module or const guarded by cfg(test) (e.g., TEST_PRIVATE_KEY) or
read from an explicit test-only env var (TEST_PRIVATE_KEY) with a test-only
fallback, then have create_test_signer and the other usage reference that symbol
instead of embedding the hex string; ensure the centralized symbol is only
compiled for tests (cfg(test)) or documented as allowlisted for CI.

Comment on lines 912 to +918
let token_bytes = parse_bytes32_from_hex(token_str).unwrap_or([0u8; 32]);
let recipient_bytes =
parse_bytes32_from_hex(recipient_str).unwrap_or([0u8; 32]);
let oracle_bytes = parse_bytes32_from_hex(oracle_str).map_err(|e| {
format!("Invalid oracle address '{}': {}", oracle_str, e)
})?;
let settler_bytes = parse_bytes32_from_hex(settler_str).map_err(|e| {
format!("Invalid settler address '{}': {}", settler_str, e)
})?;
let oracle_bytes = parse_bytes32_from_hex(oracle_str)
.map_err(|e| format!("Invalid oracle address '{oracle_str}': {e}"))?;
let settler_bytes = parse_bytes32_from_hex(settler_str)
.map_err(|e| format!("Invalid settler address '{settler_str}': {e}"))?;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent error handling for field parsing within from_batch_compact.

token_bytes and recipient_bytes (lines 912-914) use unwrap_or([0u8; 32]) for silent fallback, while oracle_bytes and settler_bytes (lines 915-918) now use ? for error propagation. This behavioral change means:

  1. Invalid token/recipient hex → silently defaults to zero bytes
  2. Invalid oracle/settler hex → returns an error

Additionally, this differs from from_permit2 (lines 534-538) which uses unwrap_or for all fields including oracle/settler.

Consider aligning the error handling strategy across all fields and order type handlers for consistency.

🤖 Prompt for AI Agents
In `@crates/solver-types/src/standards/eip7683.rs` around lines 912 - 918, In
from_batch_compact, parsing for token_bytes and recipient_bytes uses
unwrap_or([0u8;32]) while oracle_bytes and settler_bytes propagate errors via
map_err + ?; this is inconsistent with from_permit2 and causes silent defaults
for some invalid hex inputs—choose one behavior and apply it to all four fields:
either make token_bytes and recipient_bytes propagate parse errors like
oracle_bytes/settler_bytes by replacing unwrap_or with map_err(...)? or make
oracle_bytes/settler_bytes use unwrap_or([0u8;32]) to match from_permit2; update
parse_bytes32_from_hex usages in from_batch_compact accordingly so all four
fields (token_bytes, recipient_bytes, oracle_bytes, settler_bytes) follow the
same error-handling strategy.

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

🤖 Fix all issues with AI agents
In `@crates/solver-account/src/implementations/kms.rs`:
- Around line 102-108: The trait AccountInterface must document the KMS
asymmetry: add a clear doc comment on AccountInterface's sign_transaction method
explaining that some implementations (e.g., the KMS implementation in kms.rs)
may not perform transaction signing and will return an Implementation error
directing callers to use signer() (as EthereumWallet does), and clarify when
signer() should be used versus sign_transaction; also mention get_private_key()
is deprecated for KMS-backed accounts and callers should prefer signer() for
signing operations so implementations like LocalWallet and KMS are reconciled by
documentation.
🧹 Nitpick comments (2)
crates/solver-account/src/implementations/kms.rs (2)

124-126: Document the panic behavior for get_private_key() in the struct docs.

The comment notes that get_private_key() uses the default panicking implementation, which is appropriate since KMS keys cannot be exported. However, this could surprise callers who expect all AccountInterface implementations to support this method.

Consider adding this limitation to the KmsWallet struct-level documentation.

📝 Suggested documentation addition
 /// KMS-backed wallet implementation.
 ///
 /// Uses AWS KMS for all signing operations. The private key never leaves
 /// the KMS hardware security module. Public key is cached per-instance
 /// by the underlying `AwsSigner`.
+///
+/// # Note
+/// `get_private_key()` is not supported and will panic if called.
+/// Use `signer()` to obtain an `AccountSigner` for signing operations.
 pub struct KmsWallet {

160-172: Consider adding integration tests for KMS wallet.

The KMS implementation lacks test coverage. While testing against real KMS requires AWS credentials, you could add:

  1. Schema validation tests (similar to LocalWalletSchema tests)
  2. Integration tests guarded by an environment variable or feature flag for CI with LocalStack

Do you want me to generate schema validation tests for KmsWalletSchema that can run without AWS credentials?

Comment on lines +102 to +108
async fn sign_transaction(&self, _tx: &Transaction) -> Result<Signature, AccountError> {
// Transaction signing is handled by EthereumWallet using signer()
// in the delivery layer, not through AccountInterface directly
Err(AccountError::Implementation(
"Use signer() with EthereumWallet for transaction signing".into(),
))
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check AccountInterface trait definition and sign_transaction documentation
rg -n -B2 -A10 "pub trait AccountInterface" crates/solver-account/src/lib.rs

# Check how LocalWallet implements sign_transaction
rg -n -B2 -A10 "async fn sign_transaction" crates/solver-account/src/implementations/

# Check all usages of sign_transaction in the codebase
rg -n "sign_transaction" --type rust -B1 -A2

Repository: openintentsframework/oif-solver

Length of output: 7775


🏁 Script executed:

# Get full trait definition with documentation
rg -n -B10 -A15 "async fn sign_transaction.*Transaction.*Result" crates/solver-account/src/lib.rs

# Check if there's any note about optional implementations in AccountInterface
rg -n -B5 "sign_transaction" crates/solver-account/src/lib.rs | head -50

Repository: openintentsframework/oif-solver

Length of output: 1856


🏁 Script executed:

# Search for architectural decisions or comments about KMS transaction signing
rg -n "delivery layer" crates/solver-account/src/ -B5 -A5

# Check if there are integration tests that show how sign_transaction should be used
rg -n "test.*sign_transaction" crates/solver-account/src/ -B2 -A10

# Search for any docs or comments about the signer() method as alternative
rg -n -B5 -A5 "signer\(\)" crates/solver-account/src/lib.rs

Repository: openintentsframework/oif-solver

Length of output: 7695


Document or reconcile the sign_transaction asymmetry between implementations.

The AccountInterface trait requires sign_transaction as a non-optional method, but KMS deliberately returns an error directing callers to use signer() instead. LocalWallet fully implements signing, creating an inconsistency that breaks the trait contract. While the pattern (delegating to signer() for KMS) appears intentional—similar to how get_private_key() is deprecated for KMS—this design decision is not documented in the trait definition.

Either document in AccountInterface that implementations may handle transaction signing differently (with guidance on when to use sign_transaction vs signer()), or have KMS implement sign_transaction for consistency.

🤖 Prompt for AI Agents
In `@crates/solver-account/src/implementations/kms.rs` around lines 102 - 108, The
trait AccountInterface must document the KMS asymmetry: add a clear doc comment
on AccountInterface's sign_transaction method explaining that some
implementations (e.g., the KMS implementation in kms.rs) may not perform
transaction signing and will return an Implementation error directing callers to
use signer() (as EthereumWallet does), and clarify when signer() should be used
versus sign_transaction; also mention get_private_key() is deprecated for
KMS-backed accounts and callers should prefer signer() for signing operations so
implementations like LocalWallet and KMS are reconciled by documentation.

- Introduced  and  structures to manage account signing backends.
- Updated functions to support account configuration overrides, allowing for KMS and other implementations.
- Enhanced logging for account configurations during loading and seeding processes.
- Added utility functions for converting between JSON and TOML formats for account configurations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants