From f106225ba972e0fbb7ec5dffce36a2573402a97c Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 4 Mar 2026 20:52:01 +0100 Subject: [PATCH 01/12] re-introduce --- Cargo.lock | 130 ++++++++++++---- docs/add-chain-sepolia.md | 1 + docs/deploy-new-token.md | 36 ++++- hyperlane/configs/warp-config-usdt.yaml | 21 +++ ofac_diff.md | 17 +++ solver-cli/Cargo.lock | 22 +-- solver-cli/Cargo.toml | 26 ++-- solver-cli/src/commands/mod.rs | 1 + solver-cli/src/commands/ofac.rs | 190 ++++++++++++++++++++++++ solver-cli/src/main.rs | 7 +- solver-cli/src/solver/config_gen.rs | 26 +++- 11 files changed, 415 insertions(+), 62 deletions(-) create mode 100644 hyperlane/configs/warp-config-usdt.yaml create mode 100644 ofac_diff.md create mode 100644 solver-cli/src/commands/ofac.rs diff --git a/Cargo.lock b/Cargo.lock index bca0fb1..41ffc3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5260,7 +5260,7 @@ dependencies = [ [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-consensus", "alloy-network", @@ -5272,7 +5272,7 @@ dependencies = [ "aws-config", "aws-sdk-kms", "hex", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5319,9 +5319,9 @@ dependencies = [ "solver-order", "solver-pricing", "solver-service", - "solver-settlement 0.1.0", - "solver-storage", - "solver-types", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "tempfile", "thiserror 2.0.18", "tokio", @@ -5334,13 +5334,13 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "dotenvy", "regex", "rust_decimal", "serde", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5349,7 +5349,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "chrono", @@ -5364,9 +5364,9 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "solver-storage", - "solver-types", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5376,7 +5376,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-consensus", "alloy-network", @@ -5395,7 +5395,7 @@ dependencies = [ "serde", "serde_json", "solver-account", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5405,7 +5405,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5423,7 +5423,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5435,7 +5435,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5445,7 +5445,7 @@ dependencies = [ "hex", "serde", "serde_json", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "toml 0.9.12+spec-1.1.0", "tracing", @@ -5455,7 +5455,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "async-trait", @@ -5463,7 +5463,7 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5473,7 +5473,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5503,9 +5503,9 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "solver-storage", - "solver-types", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", "subtle", "thiserror 2.0.18", "tokio", @@ -5529,8 +5529,33 @@ dependencies = [ "serde_json", "sha3", "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "solver-storage", - "solver-types", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "tokio", + "toml 0.9.12+spec-1.1.0", + "tracing", +] + +[[package]] +name = "solver-settlement" +version = "0.1.0" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +dependencies = [ + "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-sol-types", + "alloy-transport", + "alloy-transport-http", + "async-trait", + "mockall", + "reqwest", + "serde", + "serde_json", + "sha3", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", "tracing", @@ -5553,8 +5578,8 @@ dependencies = [ "serde", "serde_json", "sha3", - "solver-storage", - "solver-types", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5564,7 +5589,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "async-trait", "chrono", @@ -5573,7 +5598,26 @@ dependencies = [ "redis", "serde", "serde_json", - "solver-types", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "thiserror 2.0.18", + "tokio", + "toml 0.9.12+spec-1.1.0", + "tracing", + "uuid", +] + +[[package]] +name = "solver-storage" +version = "0.1.0" +source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +dependencies = [ + "async-trait", + "chrono", + "fs2", + "redis", + "serde", + "serde_json", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5581,6 +5625,34 @@ dependencies = [ "uuid", ] +[[package]] +name = "solver-types" +version = "0.1.0" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +dependencies = [ + "alloy-consensus", + "alloy-contract", + "alloy-primitives", + "alloy-provider", + "alloy-rpc-client", + "alloy-rpc-types", + "alloy-signer", + "alloy-sol-types", + "alloy-transport", + "async-trait", + "axum", + "bytes", + "hex", + "rust_decimal", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "toml 0.9.12+spec-1.1.0", + "tracing", + "zeroize", +] + [[package]] name = "solver-types" version = "0.1.0" diff --git a/docs/add-chain-sepolia.md b/docs/add-chain-sepolia.md index ca26dd0..a11d2e3 100644 --- a/docs/add-chain-sepolia.md +++ b/docs/add-chain-sepolia.md @@ -29,6 +29,7 @@ The CLI auto-detects chains by scanning for `{CHAIN}_RPC` + `{CHAIN}_PK` pairs ## Step 2: Deploy OIF contracts to Sepolia ```bash +source .env make deploy CHAINS=sepolia ``` diff --git a/docs/deploy-new-token.md b/docs/deploy-new-token.md index 3320fe3..8945e2e 100644 --- a/docs/deploy-new-token.md +++ b/docs/deploy-new-token.md @@ -8,7 +8,7 @@ Adding a new ERC20 token (e.g. USDT) alongside USDC. Tokens route through **Cele - Foundry installed (`forge`, `cast`) - Deployer key: ```bash - PK=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + PK=52d441beb407f47811a09ed9d330320b2d336482512f26e9a5c5d3dacddc7b1e ``` --- @@ -37,14 +37,14 @@ Create `hyperlane/configs/warp-config-usdt.yaml`: anvil1: type: collateral token: "" - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + owner: "0x9f2CD91d150236BA9796124F3Dcda305C3a2086C" name: "USDT" symbol: "USDT" decimals: 6 anvil2: type: synthetic - owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + owner: "0x9f2CD91d150236BA9796124F3Dcda305C3a2086C" name: "USDT" symbol: "USDT" decimals: 6 @@ -120,20 +120,38 @@ docker run --rm \ ### anvil2 ↔ Celestia ```bash -cast send $HYP_SYNTHETIC \ +cast send $HYP_ANVIL \ "enrollRemoteRouter(uint32,bytes32)" \ 69420 $CEL_USDT_TOKEN \ --private-key $PK \ --rpc-url http://127.0.0.1:8546 -HYP_SYNTHETIC_LOWER=$(echo $HYP_SYNTHETIC | tr '[:upper:]' '[:lower:]' | cut -c 3-) +HYP_ANVIL_LOWER=$(echo $HYP_ANVIL | tr '[:upper:]' '[:lower:]' | cut -c 3-) docker run --rm \ --network solver-cli_solver-net \ --entrypoint bash \ -v "$(pwd)/hyperlane:/home/hyperlane" \ -w /home/hyperlane \ ghcr.io/celestiaorg/hyperlane-init:local \ - -c "hyp enroll-remote-router http://celestia-validator:26657 $CEL_USDT_TOKEN 31338 0x000000000000000000000000$HYP_SYNTHETIC_LOWER" + -c "hyp enroll-remote-router http://celestia-validator:26657 $CEL_USDT_TOKEN 31338 0x000000000000000000000000$HYP_ANVIL_LOWER" +``` + + +```bash +cast send $HYP_SEPOLIA \ + "enrollRemoteRouter(uint32,bytes32)" \ + 69420 $CEL_USDT_TOKEN \ + --private-key $PK \ + --rpc-url $SEPOLIA_RPC + +HYP_SEPOLIA_LOWER=$(echo $HYP_SEPOLIA | tr '[:upper:]' '[:lower:]' | cut -c 3-) +docker run --rm \ + --network solver-cli_solver-net \ + --entrypoint bash \ + -v "$(pwd)/hyperlane:/home/hyperlane" \ + -w /home/hyperlane \ + ghcr.io/celestiaorg/hyperlane-init:local \ + -c "hyp enroll-remote-router http://celestia-validator:26657 $CEL_USDT_TOKEN 11155111 0x000000000000000000000000$HYP_SEPOLIA_LOWER" ``` > Domain IDs: `131337` = anvil1, `31338` = anvil2, `69420` = Celestia. @@ -142,7 +160,9 @@ docker run --rm \ ```bash make token-add CHAIN=anvil1 SYMBOL=USDT ADDRESS=$MOCK_USDT DECIMALS=6 -make token-add CHAIN=anvil2 SYMBOL=USDT ADDRESS=$HYP_SYNTHETIC DECIMALS=6 +make token-add CHAIN=anvil2 SYMBOL=USDT ADDRESS=$HYP_ANVIL DECIMALS=6 +make token-add CHAIN=sepolia SYMBOL=USDT ADDRESS=$HYP_SEPOLIA DECIMALS=6 + ``` ## Step 6: Configure and fund @@ -150,6 +170,8 @@ make token-add CHAIN=anvil2 SYMBOL=USDT ADDRESS=$HYP_SYNTHETIC DECIMALS=6 ```bash make configure make mint SYMBOL=USDT TO=solver +make mint SYMBOL=USDT TO=solver +make mint SYMBOL=USDT TO=solver make mint SYMBOL=USDT TO=0x02120571E5804E46592f29B64fD01b1013f8fC18 ``` diff --git a/hyperlane/configs/warp-config-usdt.yaml b/hyperlane/configs/warp-config-usdt.yaml new file mode 100644 index 0000000..064bac5 --- /dev/null +++ b/hyperlane/configs/warp-config-usdt.yaml @@ -0,0 +1,21 @@ +anvil1: + type: collateral + token: "0x912C2bFdDfcbD3777dF44eF4c20d7943C521786c" + owner: "0x9f2CD91d150236BA9796124F3Dcda305C3a2086C" + name: "USDT" + symbol: "USDT" + decimals: 6 + +anvil2: + type: synthetic + owner: "0x9f2CD91d150236BA9796124F3Dcda305C3a2086C" + name: "USDT" + symbol: "USDT" + decimals: 6 + +sepolia: + type: synthetic + owner: "0x9f2CD91d150236BA9796124F3Dcda305C3a2086C" + name: "USDT" + symbol: "USDT" + decimals: 6 diff --git a/ofac_diff.md b/ofac_diff.md new file mode 100644 index 0000000..25ac17c --- /dev/null +++ b/ofac_diff.md @@ -0,0 +1,17 @@ +# OFAC Sanctions EnforcementThe solver refuses to fill intents where the **sender** or any **recipient** address appears on the OFAC Specially Designated Nationals (SDN) list. Affected users can reclaim their tokens from the intent escrow contract after expiry.---## Updating the sanctions listRun this command from the project root:```bashsolver-cli ofac update```This downloads the official SDN XML from OFAC, extracts all sanctioned Ethereum addresses, and writes them to `.config/ofac.json`.**Recommended:** run this before deploying or on a regular schedule (e.g. weekly cron) so the list stays current.---## How enforcement works1. **At solver startup**, if `ofac_list` is set in `solver.toml`, the solver loads `.config/ofac.json` into an in-memory set.2. **On each incoming intent**, before the intent is stored or filled, the solver checks: - The **sender** (`user` field of the EIP-7683 order data) - Every **recipient** address in the intent's outputs3. If any address matches, the intent is **silently skipped** — the solver publishes an `IntentRejected` event (visible in logs) and moves on.4. The intent remains open on-chain. The submitter can call the escrow contract to reclaim their tokens after the intent's expiry time passes.---## Config`solver-cli configure` (run automatically by `make deploy`) adds the following to `.config/solver.toml` when `.config/ofac.json` exists:```toml[solver]ofac_list = ".config/ofac.json"```If the file does not exist, the line is omitted and no OFAC checking is performed.---## SourceThe SDN list is fetched from the official OFAC API:```https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML```Only entries tagged `Digital Currency Address - ETH` are extracted. +Collapse file‎oif/oif-solver/crates/solver-config/src/builders/config.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-config/src/builders/config.rs+1Lines changed: 1 addition & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -126,6 +126,7 @@ impl ConfigBuilder { id: self.solver_id, min_profitability_pct: self.min_profitability_pct, monitoring_timeout_seconds: self.monitoring_timeout_seconds, ofac_list: None, }, networks: self.networks.unwrap_or_default(), storage: StorageConfig { +Collapse file‎oif/oif-solver/crates/solver-config/src/lib.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-config/src/lib.rs+5Lines changed: 5 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -91,6 +91,11 @@ pub struct SolverConfig { /// Defaults to 28800 seconds (8 hours) if not specified. #[serde(default = "default_monitoring_timeout_seconds")] pub monitoring_timeout_seconds: u64, /// Optional path to a JSON file containing OFAC-sanctioned Ethereum addresses. /// The file must contain a JSON array of lowercase hex strings (e.g. ["0xabc...", ...]). /// When set, any intent whose sender or recipient appears in the list is silently dropped. #[serde(default)] pub ofac_list: Option,}/// Configuration for the storage backend. +Collapse file‎oif/oif-solver/crates/solver-core/src/handlers/intent.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-core/src/handlers/intent.rs+335Lines changed: 335 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -17,6 +17,7 @@ use solver_types::{ truncate_id, with_0x_prefix, Address, DiscoveryEvent, Eip7683OrderData, ExecutionDecision, ExecutionParams, Intent, OrderEvent, SolverEvent, StorageKey,};use std::collections::HashSet;use std::num::NonZeroUsize;use std::sync::Arc;use thiserror::Error;@@ -55,6 +56,9 @@ pub struct IntentHandler { /// In-memory LRU cache for fast intent deduplication to prevent race conditions /// Automatically evicts oldest entries when capacity is exceeded processed_intents: Arc>>, /// OFAC-sanctioned Ethereum addresses (lowercase hex with 0x prefix). /// Loaded once at startup from `config.solver.ofac_list` if set. ofac_addresses: HashSet,}impl IntentHandler {@@ -70,6 +74,17 @@ impl IntentHandler { cost_profit_service: Arc, config: Config, ) -> Self { let ofac_addresses = Self::load_ofac_list(config.solver.ofac_list.as_deref()); if config .solver .ofac_list .as_deref() .is_some_and(|p| !p.is_empty()) && ofac_addresses.is_empty() || config.solver.ofac_list.is_none() { tracing::warn!("OFAC sanctions list could not be loaded. Enforcement is DISABLED!"); } Self { order_service, storage,@@ -83,6 +98,41 @@ impl IntentHandler { processed_intents: Arc::new(RwLock::new(LruCache::new( NonZeroUsize::new(10000).unwrap(), ))), ofac_addresses, } } /// Load OFAC-sanctioned addresses from a JSON file. /// /// Returns an empty set if no path is configured, the file is missing, /// or the file cannot be parsed. All addresses are stored in lowercase. fn load_ofac_list(path: Option<&str>) -> HashSet { let path = match path { Some(p) if !p.is_empty() => p, _ => return HashSet::new(), }; match std::fs::read_to_string(path) { Ok(content) => match serde_json::from_str::>(&content) { Ok(addrs) => { let set: HashSet = addrs.into_iter().map(|a| a.to_lowercase()).collect(); tracing::info!( path = %path, count = %set.len(), "Loaded OFAC sanctions list" ); set }, Err(e) => { tracing::warn!(path = %path, error = %e, "Failed to parse OFAC list"); HashSet::new() }, }, Err(e) => { tracing::warn!(path = %path, error = %e, "Failed to read OFAC list"); HashSet::new() }, } }@@ -126,6 +176,52 @@ impl IntentHandler { return Ok(()); } // OFAC sanctions check — runs before storing to avoid polluting the dedup cache // with addresses that will always be rejected. if !self.ofac_addresses.is_empty() { if let Ok(order_data) = serde_json::from_value::(intent.data.clone()) { // Check the order sender (user field). let user_addr = order_data.user.to_lowercase(); if self.ofac_addresses.contains(&user_addr) { tracing::warn!( intent_id = %intent.id, address = %user_addr, "Intent rejected: sender is on OFAC sanctions list" ); self.event_bus .publish(SolverEvent::Discovery(DiscoveryEvent::IntentRejected { intent_id: intent.id, reason: "Sender address is on OFAC sanctions list".to_string(), })) .ok(); return Ok(()); } // Check every output recipient. for output in &order_data.outputs { // recipient is bytes32; the Ethereum address occupies the last 20 bytes. let addr_bytes = &output.recipient[12..]; let hex_str: String = addr_bytes.iter().map(|b| format!("{:02x}", b)).collect(); let recipient_addr = format!("0x{}", hex_str); if self.ofac_addresses.contains(&recipient_addr) { tracing::warn!( intent_id = %intent.id, address = %recipient_addr, "Intent rejected: recipient is on OFAC sanctions list" ); self.event_bus .publish(SolverEvent::Discovery(DiscoveryEvent::IntentRejected { intent_id: intent.id, reason: "Recipient address is on OFAC sanctions list".to_string(), })) .ok(); return Ok(()); } } } } // Store intent immediately to prevent race conditions with duplicate discovery // This claims the intent ID slot before we start the potentially slow validation process self.storage@@ -1102,4 +1198,243 @@ mod tests { _ => panic!("Expected Preparing event"), } } // ------------------------------------------------------------------------- // OFAC tests // ------------------------------------------------------------------------- /// Write a JSON array of addresses to a temp file and return the path. fn write_ofac_temp_file(addresses: &[&str]) -> std::path::PathBuf { let path = std::env::temp_dir().join(format!( "ofac_test_{}.json", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .subsec_nanos() )); let json = serde_json::to_string(addresses).unwrap(); std::fs::write(&path, json).unwrap(); path } /// Build an intent whose `data` is a fully populated `Eip7683OrderData` JSON. fn intent_with_order_data(order_data: &solver_types::Eip7683OrderData) -> Intent { let data = serde_json::to_value(order_data).unwrap(); IntentBuilder::new().with_data(data).build() } /// Minimal handler wired to a single-use event bus, ready for OFAC tests. fn make_handler_with_config( config: Config, mock_storage: MockStorageInterface, ) -> (IntentHandler, tokio::sync::broadcast::Receiver) { use solver_account::MockAccountInterface; use solver_pricing::{MockPricingInterface, PricingService}; let storage = Arc::new(StorageService::new(Box::new(mock_storage))); let order_service = Arc::new(OrderService::new( HashMap::new(), Box::new(MockExecutionStrategy::new()), )); let state_machine = Arc::new(OrderStateMachine::new(storage.clone())); let event_bus = EventBus::new(100); let receiver = event_bus.subscribe(); let delivery = Arc::new(solver_delivery::DeliveryService::new(HashMap::new(), 1, 20)); let mut mock_pricing = MockPricingInterface::new(); mock_pricing .expect_wei_to_currency() .returning(|_, _| Box::pin(async { Ok("0.01".to_string()) })); mock_pricing .expect_convert_asset() .returning(|_, _, amount| { let amount = amount.to_string(); Box::pin(async move { Ok(amount) }) }); mock_pricing .expect_get_supported_pairs() .returning(|| Box::pin(async { vec![] })); let pricing = Arc::new(PricingService::new(Box::new(mock_pricing), Vec::new())); let token_manager = Arc::new(TokenManager::new( Default::default(), delivery.clone(), Arc::new(solver_account::AccountService::new(Box::new( MockAccountInterface::new(), ))), )); let cost_profit = Arc::new(CostProfitService::new( pricing, delivery.clone(), token_manager.clone(), Arc::new(StorageService::new(Box::new(MockStorageInterface::new()))), )); let handler = IntentHandler::new( order_service, storage, state_machine, event_bus, delivery, create_test_address(), token_manager, cost_profit, config, ); (handler, receiver) } #[tokio::test] async fn test_ofac_sender_blocked() { // The sanctioned sender address (matches a real Lazarus Group address format). let sanctioned = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ofac_file = write_ofac_temp_file(&[sanctioned]); let order_data = Eip7683OrderDataBuilder::new() .user(sanctioned) // <- sanctioned sender .build(); let intent = intent_with_order_data(&order_data); let intent_id = intent.id.clone(); let mut mock_storage = MockStorageInterface::new(); // Deduplication check passes (intent not seen before). mock_storage .expect_exists() .times(1) .returning(|_| Box::pin(async { Ok(false) })); // set_bytes must NOT be called — OFAC check returns before storing. let mut config = create_test_config(); config.solver.ofac_list = Some(ofac_file.to_string_lossy().to_string()); let (handler, mut receiver) = make_handler_with_config(config, mock_storage); let result = handler.handle(intent).await; assert!(result.is_ok(), "handler should not error on OFAC rejection"); // Expect IntentRejected event with a "sender" reason. let event = tokio::time::timeout(std::time::Duration::from_millis(200), receiver.recv()) .await .expect("expected an event within timeout") .expect("event channel closed"); match event { SolverEvent::Discovery(solver_types::DiscoveryEvent::IntentRejected { intent_id: eid, reason, }) => { assert_eq!(eid, intent_id); assert!( reason.to_lowercase().contains("sender"), "reason should mention 'sender', got: {reason}" ); }, other => panic!("expected IntentRejected, got {other:?}"), } let _ = std::fs::remove_file(ofac_file); } #[tokio::test] async fn test_ofac_recipient_blocked() { // Build a bytes32 recipient whose last 20 bytes spell out 0xbb...bb. let mut recipient_bytes32 = [0u8; 32]; recipient_bytes32[12..].copy_from_slice(&[0xbbu8; 20]); let sanctioned_recipient = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let ofac_file = write_ofac_temp_file(&[sanctioned_recipient]); let order_data = Eip7683OrderDataBuilder::new() .outputs(vec![solver_types::standards::eip7683::MandateOutput { oracle: [0u8; 32], settler: [0u8; 32], chain_id: alloy_primitives::U256::from(137), token: [0u8; 32], amount: alloy_primitives::U256::from(1u64), recipient: recipient_bytes32, // <- sanctioned recipient call: vec![], context: vec![], }]) .build(); let intent = intent_with_order_data(&order_data); let intent_id = intent.id.clone(); let mut mock_storage = MockStorageInterface::new(); mock_storage .expect_exists() .times(1) .returning(|_| Box::pin(async { Ok(false) })); let mut config = create_test_config(); config.solver.ofac_list = Some(ofac_file.to_string_lossy().to_string()); let (handler, mut receiver) = make_handler_with_config(config, mock_storage); let result = handler.handle(intent).await; assert!(result.is_ok()); let event = tokio::time::timeout(std::time::Duration::from_millis(200), receiver.recv()) .await .expect("expected an event within timeout") .expect("event channel closed"); match event { SolverEvent::Discovery(solver_types::DiscoveryEvent::IntentRejected { intent_id: eid, reason, }) => { assert_eq!(eid, intent_id); assert!( reason.to_lowercase().contains("recipient"), "reason should mention 'recipient', got: {reason}" ); }, other => panic!("expected IntentRejected, got {other:?}"), } let _ = std::fs::remove_file(ofac_file); } #[tokio::test] async fn test_ofac_unlisted_address_passes_through() { // OFAC list contains only one address that is NOT used in the intent. let ofac_file = write_ofac_temp_file(&["0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"]); // Default builder uses a different user address (0x1234...7890). let order_data = Eip7683OrderDataBuilder::new().build(); let intent = intent_with_order_data(&order_data); let mut mock_storage = MockStorageInterface::new(); mock_storage .expect_exists() .times(1) .returning(|_| Box::pin(async { Ok(false) })); // The handler reaches the store step — expect at least one set_bytes call. mock_storage .expect_set_bytes() .times(1..) .returning(|_, _, _, _| Box::pin(async { Ok(()) })); let mut config = create_test_config(); config.solver.ofac_list = Some(ofac_file.to_string_lossy().to_string()); let (handler, _receiver) = make_handler_with_config(config, mock_storage); // The handler will attempt order validation (no order impl → rejected), but that // happens *after* the OFAC check, proving the intent was not blocked by OFAC. let result = handler.handle(intent).await; assert!( result.is_ok(), "handler should not error when address is not sanctioned" ); let _ = std::fs::remove_file(ofac_file); }} +Collapse file‎oif/oif-solver/crates/solver-service/src/config_merge.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-service/src/config_merge.rs+1Lines changed: 1 addition & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -198,6 +198,7 @@ fn build_solver_config(solver_id: &str, defaults: &SeedDefaults) -> SolverConfig id: solver_id.to_string(), min_profitability_pct: defaults.min_profitability_pct, monitoring_timeout_seconds: defaults.monitoring_timeout_seconds, ofac_list: None, }} +Collapse file‎oracle-operator/src/main.rs‎Copy file name to clipboardExpand all lines: oracle-operator/src/main.rs+3-3Lines changed: 3 additions & 3 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -48,12 +48,12 @@ fn setup_logging(log_dir: &PathBuf) -> Result Result<()> { // Setup logging to ./logs/oracle let log_dir = PathBuf::from("./logs/oracle"); // Setup logging to ./.logs/oracle let log_dir = PathBuf::from("./.logs/oracle"); let _guard = setup_logging(&log_dir)?; info!("Starting Oracle Operator Service"); info!("Logs written to {:?}", log_dir); info!("Logs written to {:?}", log_dir.canonicalize().unwrap_or(log_dir.clone())); // Load config let config_path = +Collapse file‎scripts/start-frontend.sh‎Copy file name to clipboardExpand all lines: scripts/start-frontend.sh+10-10Lines changed: 10 additions & 10 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -2,14 +2,14 @@set -euo pipefail# Install frontend deps and start the backend API + Vite dev server.# PIDs are written to logs/*.pid for cleanup.# PIDs are written to .logs/*.pid for cleanup.# Usage:# ./scripts/start-frontend.shsource "$(dirname "${BASH_SOURCE[0]}")/common.sh"cd "$PROJECT_ROOT"mkdir -p logsmkdir -p .logs# ── Install dependencies ──────────────────────────────────────────────────────@@ -27,34 +27,34 @@ cd "$PROJECT_ROOT"step "Starting frontend API server (port 3001)..."cd frontendnode server.js > ../logs/frontend-backend.log 2>&1 &node server.js > ../.logs/frontend-backend.log 2>&1 &BACKEND_PID=$!echo "$BACKEND_PID" > ../logs/frontend-backend.pidecho "$BACKEND_PID" > ../.logs/frontend-backend.pidcd "$PROJECT_ROOT"sleep 2if kill -0 $BACKEND_PID 2>/dev/null; then success "Frontend backend running (PID: $BACKEND_PID)"else error "Backend failed to start. Check logs/frontend-backend.log" tail -5 logs/frontend-backend.log error "Backend failed to start. Check .logs/frontend-backend.log" tail -5 .logs/frontend-backend.log exit 1fi# ── Vite dev server ───────────────────────────────────────────────────────────step "Starting frontend (Vite dev server on port 5173)..."cd frontendnpx vite --host > ../logs/frontend-vite.log 2>&1 &npx vite --host > ../.logs/frontend-vite.log 2>&1 &FRONTEND_PID=$!echo "$FRONTEND_PID" > ../logs/frontend-vite.pidecho "$FRONTEND_PID" > ../.logs/frontend-vite.pidcd "$PROJECT_ROOT"sleep 3if kill -0 $FRONTEND_PID 2>/dev/null; then success "Frontend running (PID: $FRONTEND_PID)"else error "Frontend failed to start. Check logs/frontend-vite.log" tail -5 logs/frontend-vite.log error "Frontend failed to start. Check .logs/frontend-vite.log" tail -5 .logs/frontend-vite.log exit 1fi +Collapse file‎scripts/start-network.sh‎Copy file name to clipboardExpand all lines: scripts/start-network.sh-6Lines changed: 0 additions & 6 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -22,12 +22,6 @@ step "Stopping any running services..."make stop 2>/dev/null || truesuccess "Services stopped"# ── Clean previous state ─────────────────────────────────────────────────────step "Cleaning previous state..."make clean 2>/dev/null || truesuccess "Cleaned"# ── Build Docker images ──────────────────────────────────────────────────────if [ "$SKIP_DOCKER" = false ]; then +Collapse file‎scripts/start-services.sh‎Copy file name to clipboardExpand all lines: scripts/start-services.sh+14-14Lines changed: 14 additions & 14 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -3,59 +3,59 @@ set -euo pipefail# Start aggregator, solver, and oracle operator in the background.# Requires setup to have been run (./scripts/setup.sh).# PIDs are written to logs/*.pid for cleanup.# PIDs are written to .logs/*.pid for cleanup.# Usage:# ./scripts/start-services.shsource "$(dirname "${BASH_SOURCE[0]}")/common.sh"cd "$PROJECT_ROOT"mkdir -p logsmkdir -p .logs# ── Aggregator ────────────────────────────────────────────────────────────────step "Starting OIF Aggregator (port 4000)..."make aggregator > logs/aggregator.log 2>&1 &make aggregator > .logs/aggregator.log 2>&1 &AGG_PID=$!echo "$AGG_PID" > logs/aggregator.pidecho "$AGG_PID" > .logs/aggregator.pidsleep 3if kill -0 $AGG_PID 2>/dev/null; then success "Aggregator running (PID: $AGG_PID)"else error "Aggregator failed to start. Check logs/aggregator.log" tail -5 logs/aggregator.log error "Aggregator failed to start. Check .logs/aggregator.log" tail -5 .logs/aggregator.log exit 1fi# ── Solver ────────────────────────────────────────────────────────────────────step "Starting OIF Solver (port 3000)..."make solver > logs/solver.log 2>&1 &make solver > .logs/solver.log 2>&1 &SOLVER_PID=$!echo "$SOLVER_PID" > logs/solver.pidecho "$SOLVER_PID" > .logs/solver.pidsleep 5if kill -0 $SOLVER_PID 2>/dev/null; then success "Solver running (PID: $SOLVER_PID)"else error "Solver failed to start. Check logs/solver.log" tail -5 logs/solver.log error "Solver failed to start. Check .logs/solver.log" tail -5 .logs/solver.log exit 1fi# ── Oracle Operator ───────────────────────────────────────────────────────────step "Starting Oracle Operator..."make operator > logs/operator.log 2>&1 &make operator > .logs/operator.log 2>&1 &OPERATOR_PID=$!echo "$OPERATOR_PID" > logs/operator.pidecho "$OPERATOR_PID" > .logs/operator.pidsleep 3if kill -0 $OPERATOR_PID 2>/dev/null; then success "Oracle Operator running (PID: $OPERATOR_PID)"else error "Operator failed to start. Check logs/operator.log" tail -5 logs/operator.log error "Operator failed to start. Check .logs/operator.log" tail -5 .logs/operator.log exit 1fi +Collapse file‎solver-cli/src/commands/mod.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/commands/mod.rs+1Lines changed: 1 addition & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -5,6 +5,7 @@ pub mod deploy;pub mod fund;pub mod init;pub mod intent;pub mod ofac;pub mod order;pub mod rebalancer;pub mod solver; +Collapse file‎solver-cli/src/commands/ofac.rs‎Copy file name to clipboard+190Lines changed: 190 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -0,0 +1,190 @@use anyhow::Result;use clap::Subcommand;use std::collections::HashSet;use std::env;use std::path::PathBuf;use crate::utils::*;use crate::OutputFormat;#[derive(Subcommand)]pub enum OfacCommand { /// Update the OFAC sanctions list from the official SDN source Update { /// Project directory #[arg(long)] dir: Option, },}impl OfacCommand { pub async fn run(self, output: OutputFormat) -> Result<()> { match self { OfacCommand::Update { dir } => Self::update(dir, output).await, } } async fn update(dir: Option, output: OutputFormat) -> Result<()> { let out = OutputFormatter::new(output); let project_dir = dir.unwrap_or_else(|| env::current_dir().unwrap()); let config_dir = project_dir.join(".config"); out.header("Updating OFAC Sanctions List"); let urls = [ "https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML", "https://www.treasury.gov/ofac/downloads/sdn.xml", ]; let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(120)) .user_agent("Mozilla/5.0 (compatible; solver-cli/1.0; +https://github.com/celestiaorg)") .build()?; let mut last_status = None; let mut xml_opt: Option = None; for url in &urls { print_info(&format!("Source: {}", url)); let response = client.get(*url).send().await?; if response.status().is_success() { xml_opt = Some(response.text().await?); break; } print_info(&format!(" → HTTP {} — trying next URL", response.status())); last_status = Some(response.status()); } let xml = xml_opt.ok_or_else(|| { anyhow::anyhow!( "Failed to fetch OFAC SDN list: HTTP {}", last_status .map(|s| s.to_string()) .unwrap_or_else(|| "unknown".into()) ) })?; print_info(&format!("Downloaded {} bytes", xml.len())); let addresses = parse_eth_addresses_from_sdn_xml(&xml); let count = addresses.len(); print_info(&format!("Found {} Ethereum addresses", count)); tokio::fs::create_dir_all(&config_dir).await?; let ofac_path = config_dir.join("ofac.json"); let json = serde_json::to_string_pretty(&addresses)?; tokio::fs::write(&ofac_path, &json).await?; print_success(&format!("Saved to: {}", ofac_path.display())); print_info("Run 'solver-cli configure' to include the list in the solver config."); if out.is_json() { out.json(&serde_json::json!({ "count": count, "path": ofac_path.display().to_string(), }))?; } Ok(()) }}/// Parse Ethereum addresses from an OFAC SDN XML document.////// Scans for `` blocks that contain any `Digital Currency Address` entry/// (ETH, ERC-20 tokens like USDC/USDT, etc.) and extracts any ``/// that matches Ethereum address format (0x + 40 hex digits).pub fn parse_eth_addresses_from_sdn_xml(xml: &str) -> Vec { let mut addresses: HashSet = HashSet::new(); // Each ... block may contain one crypto address entry. for block in xml.split("").skip(1) { let block = match block.split("").next() { Some(b) => b, None => continue, }; if !block.contains("Digital Currency Address") { continue; } let start = match block.find("") { Some(i) => i + "".len(), None => continue, }; let end = match block.find("") { Some(i) => i, None => continue, }; if end <= start { continue; } let raw = block[start..end].trim(); // Normalise to lowercase with 0x prefix let addr = if raw.starts_with("0x") || raw.starts_with("0X") { raw.to_lowercase() } else { format!("0x{}", raw.to_lowercase()) }; // Ethereum addresses are 42 chars: "0x" + 40 hex digits if addr.len() == 42 && addr[2..].chars().all(|c| c.is_ascii_hexdigit()) { addresses.insert(addr); } } let mut sorted: Vec = addresses.into_iter().collect(); sorted.sort(); sorted}#[cfg(test)]mod tests { use super::*; #[test] fn test_parse_eth_addresses() { let xml = r#" Digital Currency Address - ETH 0xABCDEF1234567890ABCDEF1234567890ABCDEF12 Digital Currency Address - XBT 1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf Digital Currency Address - ETH 0x0000000000000000000000000000000000000001 Digital Currency Address - USDC 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF Digital Currency Address - USDT 0x1111111111111111111111111111111111111111 "#; let addrs = parse_eth_addresses_from_sdn_xml(xml); assert_eq!(addrs.len(), 4); assert!(addrs.contains(&"0xabcdef1234567890abcdef1234567890abcdef12".to_string())); assert!(addrs.contains(&"0x0000000000000000000000000000000000000001".to_string())); assert!(addrs.contains(&"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_string())); assert!(addrs.contains(&"0x1111111111111111111111111111111111111111".to_string())); // Bitcoin address must not be included assert!(!addrs.iter().any(|a| a.contains("1A1z"))); } #[test] fn test_parse_no_eth_addresses() { let xml = r#""#; let addrs = parse_eth_addresses_from_sdn_xml(xml); assert!(addrs.is_empty()); }} +Collapse file‎solver-cli/src/solver/config_gen.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/solver/config_gen.rs+9Lines changed: 9 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -90,6 +90,13 @@ decimals = {} output_oracles.push(format!("{} = [\"{}\"]", chain.chain_id, oracle)); } // Include OFAC list path if the file exists let ofac_line = if std::path::Path::new(".config/ofac.json").exists() { "ofac_list = \".config/ofac.json\"\n".to_string() } else { String::new() }; // Build mock price pairs from all configured tokens let mut price_symbols: Vec = state .chains@@ -127,6 +134,7 @@ decimals = {}id = "{solver_id}"min_profitability_pct = -1000.0 # Allow massive losses for testingmonitoring_timeout_seconds = 28800{ofac_line}# ============================================================================# HTTP API@@ -238,6 +246,7 @@ output = {{ {output_oracles} }} solver_private_key = solver_private_key, networks_section = networks_section.trim(), chain_ids_str = chain_ids_str, ofac_line = ofac_line, input_oracles = input_oracles.join(", "), output_oracles = output_oracles.join(", "), routes = routes.join("\n"), +Collapse file‎solver-cli/src/solver/runner.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/solver/runner.rs+1-1Lines changed: 1 addition & 1 deletionViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -18,7 +18,7 @@ impl SolverRunner { Self { config_path: project_dir.join(".config/solver.toml"), pid_file: project_dir.join(".config/solver.pid"), log_file: project_dir.join(".config/solver.log"), log_file: project_dir.join(".logs/solver.log"), } } +Collapse file‎solver-cli/src/main.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/main.rs+6-1Lines changed: 6 additions & 1 deletionViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -13,7 +13,7 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};use commands::{ balances::BalancesCommand, chain::ChainCommand, configure::ConfigureCommand, deploy::DeployCommand, fund::FundCommand, init::InitCommand, intent::IntentCommand, order::OrderCommand, rebalancer::RebalancerCommand, solver::SolverCommand, token::TokenCommand, ofac::OfacCommand, order::OrderCommand, rebalancer::RebalancerCommand, solver::SolverCommand, token::TokenCommand,};#[derive(Parser)]@@ -76,6 +76,10 @@ enum Commands { /// Check token balances on all chains Balances(BalancesCommand), /// OFAC sanctions list management #[command(subcommand)] Ofac(OfacCommand), /// Rebalancer service management #[command(subcommand)] Rebalancer(RebalancerCommand),@@ -113,6 +117,7 @@ async fn main() -> anyhow::Result<()> { Commands::Intent(cmd) => cmd.run(cli.output).await, Commands::Order(cmd) => cmd.run(cli.output).await, Commands::Balances(cmd) => cmd.run(cli.output).await, Commands::Ofac(cmd) => cmd.run(cli.output).await, Commands::Rebalancer(cmd) => cmd.run(cli.output).await, }; +Collapse file‎.gitignore‎Copy file name to clipboardExpand all lines: .gitignore+2-2Lines changed: 2 additions & 2 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -2,8 +2,6 @@**/out/**/cache/**/broadcast/logs/**/logs/.anvil1.pid.anvil2.pid.env@@ -14,6 +12,8 @@ config/.DS_Store.claude.config/.logs/**/logs/# Nested dependency lockfiles (e.g. oif-contracts libs) — reduces PR diff**/lib/**/package-lock.json +Collapse file‎Makefile‎Copy file name to clipboardExpand all lines: Makefile+1-1Lines changed: 1 addition & 1 deletionViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -467,7 +467,7 @@ mvp:## clean: Remove generated files and Docker volumesclean: @rm -rf .config @rm -rf logs @rm -rf .logs @rm -f .anvil1.pid .anvil2.pid @docker compose down -v 2>/dev/null || true @# Clean Hyperlane deployment artifacts so hyperlane-init redeploys on next start +Collapse file‎mvp.sh‎Copy file name to clipboardExpand all lines: mvp.sh+2-2Lines changed: 2 additions & 2 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -40,10 +40,10 @@ cleanup() { echo "" step "Shutting down services..." # Kill all tracked PIDs for pidfile in logs/*.pid; do for pidfile in .logs/*.pid; do [ -f "$pidfile" ] && kill "$(cat "$pidfile")" 2>/dev/null || true done rm -f logs/*.pid rm -f .logs/*.pid make stop 2>/dev/null || true success "All services stopped" exit 0 \ No newline at end of file diff --git a/solver-cli/Cargo.lock b/solver-cli/Cargo.lock index 22f777a..1be6285 100644 --- a/solver-cli/Cargo.lock +++ b/solver-cli/Cargo.lock @@ -5073,7 +5073,7 @@ dependencies = [ [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-consensus", "alloy-network", @@ -5142,7 +5142,7 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "dotenvy", "regex", @@ -5157,7 +5157,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "chrono", @@ -5184,7 +5184,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-consensus", "alloy-network", @@ -5213,7 +5213,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5243,7 +5243,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5263,7 +5263,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "async-trait", @@ -5281,7 +5281,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5328,7 +5328,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5353,7 +5353,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "async-trait", "chrono", @@ -5373,7 +5373,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" dependencies = [ "alloy-consensus", "alloy-contract", diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index 316a77b..ead4a67 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -92,23 +92,23 @@ futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } sha3 = { version = "0.10", optional = true } -solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-settlement = { path = "../solver-settlement", optional = true } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", optional = true } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["oif-interfaces"], optional = true } +solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-settlement = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", features = ["oif-interfaces"], optional = true } [dev-dependencies] assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" -solver-settlement = { path = "../solver-settlement", features = ["testing"] } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["testing"] } +solver-settlement = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", features = ["testing"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", features = ["testing"] } tempfile = "3.8" diff --git a/solver-cli/src/commands/mod.rs b/solver-cli/src/commands/mod.rs index d3a425e..772b81a 100644 --- a/solver-cli/src/commands/mod.rs +++ b/solver-cli/src/commands/mod.rs @@ -6,6 +6,7 @@ pub mod deploy; pub mod fund; pub mod init; pub mod intent; +pub mod ofac; pub mod order; pub mod rebalancer; pub mod solver; diff --git a/solver-cli/src/commands/ofac.rs b/solver-cli/src/commands/ofac.rs new file mode 100644 index 0000000..ad1a618 --- /dev/null +++ b/solver-cli/src/commands/ofac.rs @@ -0,0 +1,190 @@ +use anyhow::Result; +use clap::Subcommand; +use std::collections::HashSet; +use std::env; +use std::path::PathBuf; + +use crate::utils::*; +use crate::OutputFormat; + +#[derive(Subcommand)] +pub enum OfacCommand { + /// Update the OFAC sanctions list from the official SDN source + Update { + /// Project directory + #[arg(long)] + dir: Option, + }, +} + +impl OfacCommand { + pub async fn run(self, output: OutputFormat) -> Result<()> { + match self { + OfacCommand::Update { dir } => Self::update(dir, output).await, + } + } + + async fn update(dir: Option, output: OutputFormat) -> Result<()> { + let out = OutputFormatter::new(output); + let project_dir = dir.unwrap_or_else(|| env::current_dir().unwrap()); + let config_dir = project_dir.join(".config"); + + out.header("Updating OFAC Sanctions List"); + + let urls = [ + "https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML", + "https://www.treasury.gov/ofac/downloads/sdn.xml", + ]; + + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .user_agent("Mozilla/5.0 (compatible; solver-cli/1.0; +https://github.com/celestiaorg)") + .build()?; + + let mut last_status = None; + let mut xml_opt: Option = None; + + for url in &urls { + print_info(&format!("Source: {}", url)); + let response = client.get(*url).send().await?; + if response.status().is_success() { + xml_opt = Some(response.text().await?); + break; + } + print_info(&format!(" → HTTP {} — trying next URL", response.status())); + last_status = Some(response.status()); + } + + let xml = xml_opt.ok_or_else(|| { + anyhow::anyhow!( + "Failed to fetch OFAC SDN list: HTTP {}", + last_status + .map(|s| s.to_string()) + .unwrap_or_else(|| "unknown".into()) + ) + })?; + + print_info(&format!("Downloaded {} bytes", xml.len())); + + let addresses = parse_eth_addresses_from_sdn_xml(&xml); + let count = addresses.len(); + print_info(&format!("Found {} Ethereum addresses", count)); + + tokio::fs::create_dir_all(&config_dir).await?; + let ofac_path = config_dir.join("ofac.json"); + let json = serde_json::to_string_pretty(&addresses)?; + tokio::fs::write(&ofac_path, &json).await?; + + print_success(&format!("Saved to: {}", ofac_path.display())); + print_info("Run 'solver-cli configure' to include the list in the solver config."); + + if out.is_json() { + out.json(&serde_json::json!({ + "count": count, + "path": ofac_path.display().to_string(), + }))?; + } + + Ok(()) + } +} + +/// Parse Ethereum addresses from an OFAC SDN XML document. +/// +/// Scans for `` blocks that contain any `Digital Currency Address` entry +/// (ETH, ERC-20 tokens like USDC/USDT, etc.) and extracts any `` +/// that matches Ethereum address format (0x + 40 hex digits). +pub fn parse_eth_addresses_from_sdn_xml(xml: &str) -> Vec { + let mut addresses: HashSet = HashSet::new(); + + // Each ... block may contain one crypto address entry. + for block in xml.split("").skip(1) { + let block = match block.split("").next() { + Some(b) => b, + None => continue, + }; + + if !block.contains("Digital Currency Address") { + continue; + } + + let start = match block.find("") { + Some(i) => i + "".len(), + None => continue, + }; + let end = match block.find("") { + Some(i) => i, + None => continue, + }; + + if end <= start { + continue; + } + + let raw = block[start..end].trim(); + + // Normalise to lowercase with 0x prefix + let addr = if raw.starts_with("0x") || raw.starts_with("0X") { + raw.to_lowercase() + } else { + format!("0x{}", raw.to_lowercase()) + }; + + // Ethereum addresses are 42 chars: "0x" + 40 hex digits + if addr.len() == 42 && addr[2..].chars().all(|c| c.is_ascii_hexdigit()) { + addresses.insert(addr); + } + } + + let mut sorted: Vec = addresses.into_iter().collect(); + sorted.sort(); + sorted +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_eth_addresses() { + let xml = r#" + + + Digital Currency Address - ETH + 0xABCDEF1234567890ABCDEF1234567890ABCDEF12 + + + Digital Currency Address - XBT + 1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf + + + Digital Currency Address - ETH + 0x0000000000000000000000000000000000000001 + + + Digital Currency Address - USDC + 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF + + + Digital Currency Address - USDT + 0x1111111111111111111111111111111111111111 + + +"#; + let addrs = parse_eth_addresses_from_sdn_xml(xml); + assert_eq!(addrs.len(), 4); + assert!(addrs.contains(&"0xabcdef1234567890abcdef1234567890abcdef12".to_string())); + assert!(addrs.contains(&"0x0000000000000000000000000000000000000001".to_string())); + assert!(addrs.contains(&"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_string())); + assert!(addrs.contains(&"0x1111111111111111111111111111111111111111".to_string())); + // Bitcoin address must not be included + assert!(!addrs.iter().any(|a| a.contains("1A1z"))); + } + + #[test] + fn test_parse_no_eth_addresses() { + let xml = r#""#; + let addrs = parse_eth_addresses_from_sdn_xml(xml); + assert!(addrs.is_empty()); + } +} diff --git a/solver-cli/src/main.rs b/solver-cli/src/main.rs index adc0042..82cc1f4 100644 --- a/solver-cli/src/main.rs +++ b/solver-cli/src/main.rs @@ -13,7 +13,7 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter}; use commands::{ account::AccountCommand, balances::BalancesCommand, chain::ChainCommand, configure::ConfigureCommand, deploy::DeployCommand, fund::FundCommand, init::InitCommand, - intent::IntentCommand, order::OrderCommand, rebalancer::RebalancerCommand, + intent::IntentCommand, ofac::OfacCommand, order::OrderCommand, rebalancer::RebalancerCommand, solver::SolverCommand, token::TokenCommand, }; @@ -77,6 +77,10 @@ enum Commands { /// Check token balances on all chains Balances(BalancesCommand), + /// OFAC sanctions list management + #[command(subcommand)] + Ofac(OfacCommand), + /// Rebalancer service management #[command(subcommand)] Rebalancer(RebalancerCommand), @@ -118,6 +122,7 @@ async fn main() -> anyhow::Result<()> { Commands::Intent(cmd) => cmd.run(cli.output).await, Commands::Order(cmd) => cmd.run(cli.output).await, Commands::Balances(cmd) => cmd.run(cli.output).await, + Commands::Ofac(cmd) => cmd.run(cli.output).await, Commands::Rebalancer(cmd) => cmd.run(cli.output).await, Commands::Account(cmd) => cmd.run().await, }; diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index 69f41ee..4b8e76f 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -101,6 +101,29 @@ decimals = {} output_oracles.push(format!("{} = [\"{}\"]", chain.chain_id, oracle)); } + // Build mock price pairs from all configured tokens + let mut price_symbols: Vec = state + .chains + .values() + .flat_map(|c| c.tokens.keys().cloned()) + .collect::>() + .into_iter() + .collect(); + price_symbols.sort(); + let mock_prices = price_symbols + .iter() + .map(|sym| format!("\"{}/USD\" = \"1.0\"", sym)) + .collect::>() + .join("\n"); + + // Include OFAC list path if the file exists + let ofac_line = if std::path::Path::new(".config/ofac.json").exists() { + "ofac_list = \".config/ofac.json\"\n".to_string() + } else { + String::new() + }; + + // Build routes (all-to-all) let mut routes = Vec::new(); for &from_id in &chain_ids { @@ -125,7 +148,7 @@ min_profitability_pct = 0.0 commission_bps = 20 rate_buffer_bps = 15 monitoring_timeout_seconds = 28800 - +{ofac_line} # ============================================================================ # HTTP API # ============================================================================ @@ -257,6 +280,7 @@ output = {{ {output_oracles} }} account_section = account_section, networks_section = networks_section.trim(), chain_ids_str = chain_ids_str, + ofac_line = ofac_line, input_oracles = input_oracles.join(", "), output_oracles = output_oracles.join(", "), routes = routes.join("\n"), From 0715a7a753165e2668fd9425bb056c6db5673883 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Wed, 4 Mar 2026 21:19:34 +0100 Subject: [PATCH 02/12] cleanup --- ofac_diff.md | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 ofac_diff.md diff --git a/ofac_diff.md b/ofac_diff.md deleted file mode 100644 index 25ac17c..0000000 --- a/ofac_diff.md +++ /dev/null @@ -1,17 +0,0 @@ -# OFAC Sanctions EnforcementThe solver refuses to fill intents where the **sender** or any **recipient** address appears on the OFAC Specially Designated Nationals (SDN) list. Affected users can reclaim their tokens from the intent escrow contract after expiry.---## Updating the sanctions listRun this command from the project root:```bashsolver-cli ofac update```This downloads the official SDN XML from OFAC, extracts all sanctioned Ethereum addresses, and writes them to `.config/ofac.json`.**Recommended:** run this before deploying or on a regular schedule (e.g. weekly cron) so the list stays current.---## How enforcement works1. **At solver startup**, if `ofac_list` is set in `solver.toml`, the solver loads `.config/ofac.json` into an in-memory set.2. **On each incoming intent**, before the intent is stored or filled, the solver checks: - The **sender** (`user` field of the EIP-7683 order data) - Every **recipient** address in the intent's outputs3. If any address matches, the intent is **silently skipped** — the solver publishes an `IntentRejected` event (visible in logs) and moves on.4. The intent remains open on-chain. The submitter can call the escrow contract to reclaim their tokens after the intent's expiry time passes.---## Config`solver-cli configure` (run automatically by `make deploy`) adds the following to `.config/solver.toml` when `.config/ofac.json` exists:```toml[solver]ofac_list = ".config/ofac.json"```If the file does not exist, the line is omitted and no OFAC checking is performed.---## SourceThe SDN list is fetched from the official OFAC API:```https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML```Only entries tagged `Digital Currency Address - ETH` are extracted. -Collapse file‎oif/oif-solver/crates/solver-config/src/builders/config.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-config/src/builders/config.rs+1Lines changed: 1 addition & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -126,6 +126,7 @@ impl ConfigBuilder { id: self.solver_id, min_profitability_pct: self.min_profitability_pct, monitoring_timeout_seconds: self.monitoring_timeout_seconds, ofac_list: None, }, networks: self.networks.unwrap_or_default(), storage: StorageConfig { -Collapse file‎oif/oif-solver/crates/solver-config/src/lib.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-config/src/lib.rs+5Lines changed: 5 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -91,6 +91,11 @@ pub struct SolverConfig { /// Defaults to 28800 seconds (8 hours) if not specified. #[serde(default = "default_monitoring_timeout_seconds")] pub monitoring_timeout_seconds: u64, /// Optional path to a JSON file containing OFAC-sanctioned Ethereum addresses. /// The file must contain a JSON array of lowercase hex strings (e.g. ["0xabc...", ...]). /// When set, any intent whose sender or recipient appears in the list is silently dropped. #[serde(default)] pub ofac_list: Option,}/// Configuration for the storage backend. -Collapse file‎oif/oif-solver/crates/solver-core/src/handlers/intent.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-core/src/handlers/intent.rs+335Lines changed: 335 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -17,6 +17,7 @@ use solver_types::{ truncate_id, with_0x_prefix, Address, DiscoveryEvent, Eip7683OrderData, ExecutionDecision, ExecutionParams, Intent, OrderEvent, SolverEvent, StorageKey,};use std::collections::HashSet;use std::num::NonZeroUsize;use std::sync::Arc;use thiserror::Error;@@ -55,6 +56,9 @@ pub struct IntentHandler { /// In-memory LRU cache for fast intent deduplication to prevent race conditions /// Automatically evicts oldest entries when capacity is exceeded processed_intents: Arc>>, /// OFAC-sanctioned Ethereum addresses (lowercase hex with 0x prefix). /// Loaded once at startup from `config.solver.ofac_list` if set. ofac_addresses: HashSet,}impl IntentHandler {@@ -70,6 +74,17 @@ impl IntentHandler { cost_profit_service: Arc, config: Config, ) -> Self { let ofac_addresses = Self::load_ofac_list(config.solver.ofac_list.as_deref()); if config .solver .ofac_list .as_deref() .is_some_and(|p| !p.is_empty()) && ofac_addresses.is_empty() || config.solver.ofac_list.is_none() { tracing::warn!("OFAC sanctions list could not be loaded. Enforcement is DISABLED!"); } Self { order_service, storage,@@ -83,6 +98,41 @@ impl IntentHandler { processed_intents: Arc::new(RwLock::new(LruCache::new( NonZeroUsize::new(10000).unwrap(), ))), ofac_addresses, } } /// Load OFAC-sanctioned addresses from a JSON file. /// /// Returns an empty set if no path is configured, the file is missing, /// or the file cannot be parsed. All addresses are stored in lowercase. fn load_ofac_list(path: Option<&str>) -> HashSet { let path = match path { Some(p) if !p.is_empty() => p, _ => return HashSet::new(), }; match std::fs::read_to_string(path) { Ok(content) => match serde_json::from_str::>(&content) { Ok(addrs) => { let set: HashSet = addrs.into_iter().map(|a| a.to_lowercase()).collect(); tracing::info!( path = %path, count = %set.len(), "Loaded OFAC sanctions list" ); set }, Err(e) => { tracing::warn!(path = %path, error = %e, "Failed to parse OFAC list"); HashSet::new() }, }, Err(e) => { tracing::warn!(path = %path, error = %e, "Failed to read OFAC list"); HashSet::new() }, } }@@ -126,6 +176,52 @@ impl IntentHandler { return Ok(()); } // OFAC sanctions check — runs before storing to avoid polluting the dedup cache // with addresses that will always be rejected. if !self.ofac_addresses.is_empty() { if let Ok(order_data) = serde_json::from_value::(intent.data.clone()) { // Check the order sender (user field). let user_addr = order_data.user.to_lowercase(); if self.ofac_addresses.contains(&user_addr) { tracing::warn!( intent_id = %intent.id, address = %user_addr, "Intent rejected: sender is on OFAC sanctions list" ); self.event_bus .publish(SolverEvent::Discovery(DiscoveryEvent::IntentRejected { intent_id: intent.id, reason: "Sender address is on OFAC sanctions list".to_string(), })) .ok(); return Ok(()); } // Check every output recipient. for output in &order_data.outputs { // recipient is bytes32; the Ethereum address occupies the last 20 bytes. let addr_bytes = &output.recipient[12..]; let hex_str: String = addr_bytes.iter().map(|b| format!("{:02x}", b)).collect(); let recipient_addr = format!("0x{}", hex_str); if self.ofac_addresses.contains(&recipient_addr) { tracing::warn!( intent_id = %intent.id, address = %recipient_addr, "Intent rejected: recipient is on OFAC sanctions list" ); self.event_bus .publish(SolverEvent::Discovery(DiscoveryEvent::IntentRejected { intent_id: intent.id, reason: "Recipient address is on OFAC sanctions list".to_string(), })) .ok(); return Ok(()); } } } } // Store intent immediately to prevent race conditions with duplicate discovery // This claims the intent ID slot before we start the potentially slow validation process self.storage@@ -1102,4 +1198,243 @@ mod tests { _ => panic!("Expected Preparing event"), } } // ------------------------------------------------------------------------- // OFAC tests // ------------------------------------------------------------------------- /// Write a JSON array of addresses to a temp file and return the path. fn write_ofac_temp_file(addresses: &[&str]) -> std::path::PathBuf { let path = std::env::temp_dir().join(format!( "ofac_test_{}.json", std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) .unwrap() .subsec_nanos() )); let json = serde_json::to_string(addresses).unwrap(); std::fs::write(&path, json).unwrap(); path } /// Build an intent whose `data` is a fully populated `Eip7683OrderData` JSON. fn intent_with_order_data(order_data: &solver_types::Eip7683OrderData) -> Intent { let data = serde_json::to_value(order_data).unwrap(); IntentBuilder::new().with_data(data).build() } /// Minimal handler wired to a single-use event bus, ready for OFAC tests. fn make_handler_with_config( config: Config, mock_storage: MockStorageInterface, ) -> (IntentHandler, tokio::sync::broadcast::Receiver) { use solver_account::MockAccountInterface; use solver_pricing::{MockPricingInterface, PricingService}; let storage = Arc::new(StorageService::new(Box::new(mock_storage))); let order_service = Arc::new(OrderService::new( HashMap::new(), Box::new(MockExecutionStrategy::new()), )); let state_machine = Arc::new(OrderStateMachine::new(storage.clone())); let event_bus = EventBus::new(100); let receiver = event_bus.subscribe(); let delivery = Arc::new(solver_delivery::DeliveryService::new(HashMap::new(), 1, 20)); let mut mock_pricing = MockPricingInterface::new(); mock_pricing .expect_wei_to_currency() .returning(|_, _| Box::pin(async { Ok("0.01".to_string()) })); mock_pricing .expect_convert_asset() .returning(|_, _, amount| { let amount = amount.to_string(); Box::pin(async move { Ok(amount) }) }); mock_pricing .expect_get_supported_pairs() .returning(|| Box::pin(async { vec![] })); let pricing = Arc::new(PricingService::new(Box::new(mock_pricing), Vec::new())); let token_manager = Arc::new(TokenManager::new( Default::default(), delivery.clone(), Arc::new(solver_account::AccountService::new(Box::new( MockAccountInterface::new(), ))), )); let cost_profit = Arc::new(CostProfitService::new( pricing, delivery.clone(), token_manager.clone(), Arc::new(StorageService::new(Box::new(MockStorageInterface::new()))), )); let handler = IntentHandler::new( order_service, storage, state_machine, event_bus, delivery, create_test_address(), token_manager, cost_profit, config, ); (handler, receiver) } #[tokio::test] async fn test_ofac_sender_blocked() { // The sanctioned sender address (matches a real Lazarus Group address format). let sanctioned = "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; let ofac_file = write_ofac_temp_file(&[sanctioned]); let order_data = Eip7683OrderDataBuilder::new() .user(sanctioned) // <- sanctioned sender .build(); let intent = intent_with_order_data(&order_data); let intent_id = intent.id.clone(); let mut mock_storage = MockStorageInterface::new(); // Deduplication check passes (intent not seen before). mock_storage .expect_exists() .times(1) .returning(|_| Box::pin(async { Ok(false) })); // set_bytes must NOT be called — OFAC check returns before storing. let mut config = create_test_config(); config.solver.ofac_list = Some(ofac_file.to_string_lossy().to_string()); let (handler, mut receiver) = make_handler_with_config(config, mock_storage); let result = handler.handle(intent).await; assert!(result.is_ok(), "handler should not error on OFAC rejection"); // Expect IntentRejected event with a "sender" reason. let event = tokio::time::timeout(std::time::Duration::from_millis(200), receiver.recv()) .await .expect("expected an event within timeout") .expect("event channel closed"); match event { SolverEvent::Discovery(solver_types::DiscoveryEvent::IntentRejected { intent_id: eid, reason, }) => { assert_eq!(eid, intent_id); assert!( reason.to_lowercase().contains("sender"), "reason should mention 'sender', got: {reason}" ); }, other => panic!("expected IntentRejected, got {other:?}"), } let _ = std::fs::remove_file(ofac_file); } #[tokio::test] async fn test_ofac_recipient_blocked() { // Build a bytes32 recipient whose last 20 bytes spell out 0xbb...bb. let mut recipient_bytes32 = [0u8; 32]; recipient_bytes32[12..].copy_from_slice(&[0xbbu8; 20]); let sanctioned_recipient = "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"; let ofac_file = write_ofac_temp_file(&[sanctioned_recipient]); let order_data = Eip7683OrderDataBuilder::new() .outputs(vec![solver_types::standards::eip7683::MandateOutput { oracle: [0u8; 32], settler: [0u8; 32], chain_id: alloy_primitives::U256::from(137), token: [0u8; 32], amount: alloy_primitives::U256::from(1u64), recipient: recipient_bytes32, // <- sanctioned recipient call: vec![], context: vec![], }]) .build(); let intent = intent_with_order_data(&order_data); let intent_id = intent.id.clone(); let mut mock_storage = MockStorageInterface::new(); mock_storage .expect_exists() .times(1) .returning(|_| Box::pin(async { Ok(false) })); let mut config = create_test_config(); config.solver.ofac_list = Some(ofac_file.to_string_lossy().to_string()); let (handler, mut receiver) = make_handler_with_config(config, mock_storage); let result = handler.handle(intent).await; assert!(result.is_ok()); let event = tokio::time::timeout(std::time::Duration::from_millis(200), receiver.recv()) .await .expect("expected an event within timeout") .expect("event channel closed"); match event { SolverEvent::Discovery(solver_types::DiscoveryEvent::IntentRejected { intent_id: eid, reason, }) => { assert_eq!(eid, intent_id); assert!( reason.to_lowercase().contains("recipient"), "reason should mention 'recipient', got: {reason}" ); }, other => panic!("expected IntentRejected, got {other:?}"), } let _ = std::fs::remove_file(ofac_file); } #[tokio::test] async fn test_ofac_unlisted_address_passes_through() { // OFAC list contains only one address that is NOT used in the intent. let ofac_file = write_ofac_temp_file(&["0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"]); // Default builder uses a different user address (0x1234...7890). let order_data = Eip7683OrderDataBuilder::new().build(); let intent = intent_with_order_data(&order_data); let mut mock_storage = MockStorageInterface::new(); mock_storage .expect_exists() .times(1) .returning(|_| Box::pin(async { Ok(false) })); // The handler reaches the store step — expect at least one set_bytes call. mock_storage .expect_set_bytes() .times(1..) .returning(|_, _, _, _| Box::pin(async { Ok(()) })); let mut config = create_test_config(); config.solver.ofac_list = Some(ofac_file.to_string_lossy().to_string()); let (handler, _receiver) = make_handler_with_config(config, mock_storage); // The handler will attempt order validation (no order impl → rejected), but that // happens *after* the OFAC check, proving the intent was not blocked by OFAC. let result = handler.handle(intent).await; assert!( result.is_ok(), "handler should not error when address is not sanctioned" ); let _ = std::fs::remove_file(ofac_file); }} -Collapse file‎oif/oif-solver/crates/solver-service/src/config_merge.rs‎Copy file name to clipboardExpand all lines: oif/oif-solver/crates/solver-service/src/config_merge.rs+1Lines changed: 1 addition & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -198,6 +198,7 @@ fn build_solver_config(solver_id: &str, defaults: &SeedDefaults) -> SolverConfig id: solver_id.to_string(), min_profitability_pct: defaults.min_profitability_pct, monitoring_timeout_seconds: defaults.monitoring_timeout_seconds, ofac_list: None, }} -Collapse file‎oracle-operator/src/main.rs‎Copy file name to clipboardExpand all lines: oracle-operator/src/main.rs+3-3Lines changed: 3 additions & 3 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -48,12 +48,12 @@ fn setup_logging(log_dir: &PathBuf) -> Result Result<()> { // Setup logging to ./logs/oracle let log_dir = PathBuf::from("./logs/oracle"); // Setup logging to ./.logs/oracle let log_dir = PathBuf::from("./.logs/oracle"); let _guard = setup_logging(&log_dir)?; info!("Starting Oracle Operator Service"); info!("Logs written to {:?}", log_dir); info!("Logs written to {:?}", log_dir.canonicalize().unwrap_or(log_dir.clone())); // Load config let config_path = -Collapse file‎scripts/start-frontend.sh‎Copy file name to clipboardExpand all lines: scripts/start-frontend.sh+10-10Lines changed: 10 additions & 10 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -2,14 +2,14 @@set -euo pipefail# Install frontend deps and start the backend API + Vite dev server.# PIDs are written to logs/*.pid for cleanup.# PIDs are written to .logs/*.pid for cleanup.# Usage:# ./scripts/start-frontend.shsource "$(dirname "${BASH_SOURCE[0]}")/common.sh"cd "$PROJECT_ROOT"mkdir -p logsmkdir -p .logs# ── Install dependencies ──────────────────────────────────────────────────────@@ -27,34 +27,34 @@ cd "$PROJECT_ROOT"step "Starting frontend API server (port 3001)..."cd frontendnode server.js > ../logs/frontend-backend.log 2>&1 &node server.js > ../.logs/frontend-backend.log 2>&1 &BACKEND_PID=$!echo "$BACKEND_PID" > ../logs/frontend-backend.pidecho "$BACKEND_PID" > ../.logs/frontend-backend.pidcd "$PROJECT_ROOT"sleep 2if kill -0 $BACKEND_PID 2>/dev/null; then success "Frontend backend running (PID: $BACKEND_PID)"else error "Backend failed to start. Check logs/frontend-backend.log" tail -5 logs/frontend-backend.log error "Backend failed to start. Check .logs/frontend-backend.log" tail -5 .logs/frontend-backend.log exit 1fi# ── Vite dev server ───────────────────────────────────────────────────────────step "Starting frontend (Vite dev server on port 5173)..."cd frontendnpx vite --host > ../logs/frontend-vite.log 2>&1 &npx vite --host > ../.logs/frontend-vite.log 2>&1 &FRONTEND_PID=$!echo "$FRONTEND_PID" > ../logs/frontend-vite.pidecho "$FRONTEND_PID" > ../.logs/frontend-vite.pidcd "$PROJECT_ROOT"sleep 3if kill -0 $FRONTEND_PID 2>/dev/null; then success "Frontend running (PID: $FRONTEND_PID)"else error "Frontend failed to start. Check logs/frontend-vite.log" tail -5 logs/frontend-vite.log error "Frontend failed to start. Check .logs/frontend-vite.log" tail -5 .logs/frontend-vite.log exit 1fi -Collapse file‎scripts/start-network.sh‎Copy file name to clipboardExpand all lines: scripts/start-network.sh-6Lines changed: 0 additions & 6 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -22,12 +22,6 @@ step "Stopping any running services..."make stop 2>/dev/null || truesuccess "Services stopped"# ── Clean previous state ─────────────────────────────────────────────────────step "Cleaning previous state..."make clean 2>/dev/null || truesuccess "Cleaned"# ── Build Docker images ──────────────────────────────────────────────────────if [ "$SKIP_DOCKER" = false ]; then -Collapse file‎scripts/start-services.sh‎Copy file name to clipboardExpand all lines: scripts/start-services.sh+14-14Lines changed: 14 additions & 14 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -3,59 +3,59 @@ set -euo pipefail# Start aggregator, solver, and oracle operator in the background.# Requires setup to have been run (./scripts/setup.sh).# PIDs are written to logs/*.pid for cleanup.# PIDs are written to .logs/*.pid for cleanup.# Usage:# ./scripts/start-services.shsource "$(dirname "${BASH_SOURCE[0]}")/common.sh"cd "$PROJECT_ROOT"mkdir -p logsmkdir -p .logs# ── Aggregator ────────────────────────────────────────────────────────────────step "Starting OIF Aggregator (port 4000)..."make aggregator > logs/aggregator.log 2>&1 &make aggregator > .logs/aggregator.log 2>&1 &AGG_PID=$!echo "$AGG_PID" > logs/aggregator.pidecho "$AGG_PID" > .logs/aggregator.pidsleep 3if kill -0 $AGG_PID 2>/dev/null; then success "Aggregator running (PID: $AGG_PID)"else error "Aggregator failed to start. Check logs/aggregator.log" tail -5 logs/aggregator.log error "Aggregator failed to start. Check .logs/aggregator.log" tail -5 .logs/aggregator.log exit 1fi# ── Solver ────────────────────────────────────────────────────────────────────step "Starting OIF Solver (port 3000)..."make solver > logs/solver.log 2>&1 &make solver > .logs/solver.log 2>&1 &SOLVER_PID=$!echo "$SOLVER_PID" > logs/solver.pidecho "$SOLVER_PID" > .logs/solver.pidsleep 5if kill -0 $SOLVER_PID 2>/dev/null; then success "Solver running (PID: $SOLVER_PID)"else error "Solver failed to start. Check logs/solver.log" tail -5 logs/solver.log error "Solver failed to start. Check .logs/solver.log" tail -5 .logs/solver.log exit 1fi# ── Oracle Operator ───────────────────────────────────────────────────────────step "Starting Oracle Operator..."make operator > logs/operator.log 2>&1 &make operator > .logs/operator.log 2>&1 &OPERATOR_PID=$!echo "$OPERATOR_PID" > logs/operator.pidecho "$OPERATOR_PID" > .logs/operator.pidsleep 3if kill -0 $OPERATOR_PID 2>/dev/null; then success "Oracle Operator running (PID: $OPERATOR_PID)"else error "Operator failed to start. Check logs/operator.log" tail -5 logs/operator.log error "Operator failed to start. Check .logs/operator.log" tail -5 .logs/operator.log exit 1fi -Collapse file‎solver-cli/src/commands/mod.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/commands/mod.rs+1Lines changed: 1 addition & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -5,6 +5,7 @@ pub mod deploy;pub mod fund;pub mod init;pub mod intent;pub mod ofac;pub mod order;pub mod rebalancer;pub mod solver; -Collapse file‎solver-cli/src/commands/ofac.rs‎Copy file name to clipboard+190Lines changed: 190 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -0,0 +1,190 @@use anyhow::Result;use clap::Subcommand;use std::collections::HashSet;use std::env;use std::path::PathBuf;use crate::utils::*;use crate::OutputFormat;#[derive(Subcommand)]pub enum OfacCommand { /// Update the OFAC sanctions list from the official SDN source Update { /// Project directory #[arg(long)] dir: Option, },}impl OfacCommand { pub async fn run(self, output: OutputFormat) -> Result<()> { match self { OfacCommand::Update { dir } => Self::update(dir, output).await, } } async fn update(dir: Option, output: OutputFormat) -> Result<()> { let out = OutputFormatter::new(output); let project_dir = dir.unwrap_or_else(|| env::current_dir().unwrap()); let config_dir = project_dir.join(".config"); out.header("Updating OFAC Sanctions List"); let urls = [ "https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML", "https://www.treasury.gov/ofac/downloads/sdn.xml", ]; let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(120)) .user_agent("Mozilla/5.0 (compatible; solver-cli/1.0; +https://github.com/celestiaorg)") .build()?; let mut last_status = None; let mut xml_opt: Option = None; for url in &urls { print_info(&format!("Source: {}", url)); let response = client.get(*url).send().await?; if response.status().is_success() { xml_opt = Some(response.text().await?); break; } print_info(&format!(" → HTTP {} — trying next URL", response.status())); last_status = Some(response.status()); } let xml = xml_opt.ok_or_else(|| { anyhow::anyhow!( "Failed to fetch OFAC SDN list: HTTP {}", last_status .map(|s| s.to_string()) .unwrap_or_else(|| "unknown".into()) ) })?; print_info(&format!("Downloaded {} bytes", xml.len())); let addresses = parse_eth_addresses_from_sdn_xml(&xml); let count = addresses.len(); print_info(&format!("Found {} Ethereum addresses", count)); tokio::fs::create_dir_all(&config_dir).await?; let ofac_path = config_dir.join("ofac.json"); let json = serde_json::to_string_pretty(&addresses)?; tokio::fs::write(&ofac_path, &json).await?; print_success(&format!("Saved to: {}", ofac_path.display())); print_info("Run 'solver-cli configure' to include the list in the solver config."); if out.is_json() { out.json(&serde_json::json!({ "count": count, "path": ofac_path.display().to_string(), }))?; } Ok(()) }}/// Parse Ethereum addresses from an OFAC SDN XML document.////// Scans for `` blocks that contain any `Digital Currency Address` entry/// (ETH, ERC-20 tokens like USDC/USDT, etc.) and extracts any ``/// that matches Ethereum address format (0x + 40 hex digits).pub fn parse_eth_addresses_from_sdn_xml(xml: &str) -> Vec { let mut addresses: HashSet = HashSet::new(); // Each ... block may contain one crypto address entry. for block in xml.split("").skip(1) { let block = match block.split("").next() { Some(b) => b, None => continue, }; if !block.contains("Digital Currency Address") { continue; } let start = match block.find("") { Some(i) => i + "".len(), None => continue, }; let end = match block.find("") { Some(i) => i, None => continue, }; if end <= start { continue; } let raw = block[start..end].trim(); // Normalise to lowercase with 0x prefix let addr = if raw.starts_with("0x") || raw.starts_with("0X") { raw.to_lowercase() } else { format!("0x{}", raw.to_lowercase()) }; // Ethereum addresses are 42 chars: "0x" + 40 hex digits if addr.len() == 42 && addr[2..].chars().all(|c| c.is_ascii_hexdigit()) { addresses.insert(addr); } } let mut sorted: Vec = addresses.into_iter().collect(); sorted.sort(); sorted}#[cfg(test)]mod tests { use super::*; #[test] fn test_parse_eth_addresses() { let xml = r#" Digital Currency Address - ETH 0xABCDEF1234567890ABCDEF1234567890ABCDEF12 Digital Currency Address - XBT 1A1zP1eP5QGefi2DMPTfTL5SLmv7Divf Digital Currency Address - ETH 0x0000000000000000000000000000000000000001 Digital Currency Address - USDC 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF Digital Currency Address - USDT 0x1111111111111111111111111111111111111111 "#; let addrs = parse_eth_addresses_from_sdn_xml(xml); assert_eq!(addrs.len(), 4); assert!(addrs.contains(&"0xabcdef1234567890abcdef1234567890abcdef12".to_string())); assert!(addrs.contains(&"0x0000000000000000000000000000000000000001".to_string())); assert!(addrs.contains(&"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_string())); assert!(addrs.contains(&"0x1111111111111111111111111111111111111111".to_string())); // Bitcoin address must not be included assert!(!addrs.iter().any(|a| a.contains("1A1z"))); } #[test] fn test_parse_no_eth_addresses() { let xml = r#""#; let addrs = parse_eth_addresses_from_sdn_xml(xml); assert!(addrs.is_empty()); }} -Collapse file‎solver-cli/src/solver/config_gen.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/solver/config_gen.rs+9Lines changed: 9 additions & 0 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -90,6 +90,13 @@ decimals = {} output_oracles.push(format!("{} = [\"{}\"]", chain.chain_id, oracle)); } // Include OFAC list path if the file exists let ofac_line = if std::path::Path::new(".config/ofac.json").exists() { "ofac_list = \".config/ofac.json\"\n".to_string() } else { String::new() }; // Build mock price pairs from all configured tokens let mut price_symbols: Vec = state .chains@@ -127,6 +134,7 @@ decimals = {}id = "{solver_id}"min_profitability_pct = -1000.0 # Allow massive losses for testingmonitoring_timeout_seconds = 28800{ofac_line}# ============================================================================# HTTP API@@ -238,6 +246,7 @@ output = {{ {output_oracles} }} solver_private_key = solver_private_key, networks_section = networks_section.trim(), chain_ids_str = chain_ids_str, ofac_line = ofac_line, input_oracles = input_oracles.join(", "), output_oracles = output_oracles.join(", "), routes = routes.join("\n"), -Collapse file‎solver-cli/src/solver/runner.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/solver/runner.rs+1-1Lines changed: 1 addition & 1 deletionViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -18,7 +18,7 @@ impl SolverRunner { Self { config_path: project_dir.join(".config/solver.toml"), pid_file: project_dir.join(".config/solver.pid"), log_file: project_dir.join(".config/solver.log"), log_file: project_dir.join(".logs/solver.log"), } } -Collapse file‎solver-cli/src/main.rs‎Copy file name to clipboardExpand all lines: solver-cli/src/main.rs+6-1Lines changed: 6 additions & 1 deletionViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -13,7 +13,7 @@ use tracing_subscriber::{fmt, prelude::*, EnvFilter};use commands::{ balances::BalancesCommand, chain::ChainCommand, configure::ConfigureCommand, deploy::DeployCommand, fund::FundCommand, init::InitCommand, intent::IntentCommand, order::OrderCommand, rebalancer::RebalancerCommand, solver::SolverCommand, token::TokenCommand, ofac::OfacCommand, order::OrderCommand, rebalancer::RebalancerCommand, solver::SolverCommand, token::TokenCommand,};#[derive(Parser)]@@ -76,6 +76,10 @@ enum Commands { /// Check token balances on all chains Balances(BalancesCommand), /// OFAC sanctions list management #[command(subcommand)] Ofac(OfacCommand), /// Rebalancer service management #[command(subcommand)] Rebalancer(RebalancerCommand),@@ -113,6 +117,7 @@ async fn main() -> anyhow::Result<()> { Commands::Intent(cmd) => cmd.run(cli.output).await, Commands::Order(cmd) => cmd.run(cli.output).await, Commands::Balances(cmd) => cmd.run(cli.output).await, Commands::Ofac(cmd) => cmd.run(cli.output).await, Commands::Rebalancer(cmd) => cmd.run(cli.output).await, }; -Collapse file‎.gitignore‎Copy file name to clipboardExpand all lines: .gitignore+2-2Lines changed: 2 additions & 2 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -2,8 +2,6 @@**/out/**/cache/**/broadcast/logs/**/logs/.anvil1.pid.anvil2.pid.env@@ -14,6 +12,8 @@ config/.DS_Store.claude.config/.logs/**/logs/# Nested dependency lockfiles (e.g. oif-contracts libs) — reduces PR diff**/lib/**/package-lock.json -Collapse file‎Makefile‎Copy file name to clipboardExpand all lines: Makefile+1-1Lines changed: 1 addition & 1 deletionViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -467,7 +467,7 @@ mvp:## clean: Remove generated files and Docker volumesclean: @rm -rf .config @rm -rf logs @rm -rf .logs @rm -f .anvil1.pid .anvil2.pid @docker compose down -v 2>/dev/null || true @# Clean Hyperlane deployment artifacts so hyperlane-init redeploys on next start -Collapse file‎mvp.sh‎Copy file name to clipboardExpand all lines: mvp.sh+2-2Lines changed: 2 additions & 2 deletionsViewedComment on this fileMore optionsOriginal file line numberDiff line numberDiff line change@@ -40,10 +40,10 @@ cleanup() { echo "" step "Shutting down services..." # Kill all tracked PIDs for pidfile in logs/*.pid; do for pidfile in .logs/*.pid; do [ -f "$pidfile" ] && kill "$(cat "$pidfile")" 2>/dev/null || true done rm -f logs/*.pid rm -f .logs/*.pid make stop 2>/dev/null || true success "All services stopped" exit 0 \ No newline at end of file From 8975af94f171062afb790cfb9dceb8c3fd3151a4 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 5 Mar 2026 12:12:30 +0100 Subject: [PATCH 03/12] update oif-solver dep --- Cargo.lock | 59 +++++++++++++++++++++---------------------- solver-cli/Cargo.lock | 24 +++++++++--------- solver-cli/Cargo.toml | 26 +++++++++---------- 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 41ffc3f..27679a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5260,7 +5260,7 @@ dependencies = [ [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-consensus", "alloy-network", @@ -5272,7 +5272,7 @@ dependencies = [ "aws-config", "aws-sdk-kms", "hex", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5319,9 +5319,9 @@ dependencies = [ "solver-order", "solver-pricing", "solver-service", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-settlement 0.1.0", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "tempfile", "thiserror 2.0.18", "tokio", @@ -5334,13 +5334,13 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "dotenvy", "regex", "rust_decimal", "serde", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5349,7 +5349,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-primitives", "chrono", @@ -5364,9 +5364,9 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5376,7 +5376,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-consensus", "alloy-network", @@ -5395,7 +5395,7 @@ dependencies = [ "serde", "serde_json", "solver-account", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5405,7 +5405,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5423,7 +5423,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5435,7 +5435,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5445,7 +5445,7 @@ dependencies = [ "hex", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "toml 0.9.12+spec-1.1.0", "tracing", @@ -5455,7 +5455,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-primitives", "async-trait", @@ -5463,7 +5463,7 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5473,7 +5473,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5503,9 +5503,9 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "subtle", "thiserror 2.0.18", "tokio", @@ -5539,7 +5539,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5548,13 +5548,12 @@ dependencies = [ "alloy-transport", "alloy-transport-http", "async-trait", - "mockall", "reqwest", "serde", "serde_json", "sha3", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5589,7 +5588,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "async-trait", "chrono", @@ -5598,7 +5597,7 @@ dependencies = [ "redis", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=070e2d5)", + "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5628,7 +5627,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" dependencies = [ "alloy-consensus", "alloy-contract", diff --git a/solver-cli/Cargo.lock b/solver-cli/Cargo.lock index 1be6285..b6f0db4 100644 --- a/solver-cli/Cargo.lock +++ b/solver-cli/Cargo.lock @@ -1967,7 +1967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.59.0", + "windows-sys 0.48.0", ] [[package]] @@ -5073,7 +5073,7 @@ dependencies = [ [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-consensus", "alloy-network", @@ -5142,7 +5142,7 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "dotenvy", "regex", @@ -5157,7 +5157,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "chrono", @@ -5184,7 +5184,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-consensus", "alloy-network", @@ -5213,7 +5213,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5243,7 +5243,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5263,7 +5263,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "async-trait", @@ -5281,7 +5281,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5328,7 +5328,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5353,7 +5353,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "async-trait", "chrono", @@ -5373,7 +5373,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=070e2d5#070e2d55d48bcf3b9cca513acb8053f8aecf5247" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-consensus", "alloy-contract", diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index ead4a67..e0bc20d 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -92,23 +92,23 @@ futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } sha3 = { version = "0.10", optional = true } -solver-account = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-config = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-core = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-order = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-service = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-settlement = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", optional = true } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", features = ["oif-interfaces"], optional = true } +solver-account = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-config = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-core = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-order = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-service = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-settlement = { path = "../solver-settlement", optional = true } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["oif-interfaces"], optional = true } [dev-dependencies] assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" -solver-settlement = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", features = ["testing"] } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "070e2d5", features = ["testing"] } tempfile = "3.8" +solver-settlement = { path = "../solver-settlement", features = ["testing"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["testing"] } From a1d8f9046fa7e87af27531b916960b3e92e811d2 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 5 Mar 2026 17:22:29 +0100 Subject: [PATCH 04/12] rename solver-settlement to solver-settlement-impls to avoid name conflict with git dep Co-Authored-By: Claude Sonnet 4.6 --- Cargo.lock | 117 +++++++------------------------- solver-cli/Cargo.toml | 6 +- solver-cli/src/solver/engine.rs | 8 +-- solver-settlement/Cargo.toml | 8 +-- 4 files changed, 34 insertions(+), 105 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 27679a9..a0a6397 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5272,7 +5272,7 @@ dependencies = [ "aws-config", "aws-sdk-kms", "hex", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5319,9 +5319,9 @@ dependencies = [ "solver-order", "solver-pricing", "solver-service", - "solver-settlement 0.1.0", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-settlement-impls", + "solver-storage", + "solver-types", "tempfile", "thiserror 2.0.18", "tokio", @@ -5340,7 +5340,7 @@ dependencies = [ "regex", "rust_decimal", "serde", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5364,9 +5364,9 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-settlement", + "solver-storage", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5395,7 +5395,7 @@ dependencies = [ "serde", "serde_json", "solver-account", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5423,7 +5423,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5445,7 +5445,7 @@ dependencies = [ "hex", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types", "thiserror 2.0.18", "toml 0.9.12+spec-1.1.0", "tracing", @@ -5463,7 +5463,7 @@ dependencies = [ "rust_decimal", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5503,9 +5503,9 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-settlement", + "solver-storage", + "solver-types", "subtle", "thiserror 2.0.18", "tokio", @@ -5517,25 +5517,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "solver-settlement" -version = "0.1.0" -dependencies = [ - "alloy-primitives", - "alloy-provider", - "alloy-rpc-types", - "alloy-sol-types", - "async-trait", - "serde_json", - "sha3", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "tokio", - "toml 0.9.12+spec-1.1.0", - "tracing", -] - [[package]] name = "solver-settlement" version = "0.1.0" @@ -5548,12 +5529,13 @@ dependencies = [ "alloy-transport", "alloy-transport-http", "async-trait", + "mockall", "reqwest", "serde", "serde_json", "sha3", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", + "solver-storage", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5561,25 +5543,19 @@ dependencies = [ ] [[package]] -name = "solver-settlement" +name = "solver-settlement-impls" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rpc-types", "alloy-sol-types", - "alloy-transport", - "alloy-transport-http", "async-trait", - "mockall", - "reqwest", - "serde", "serde_json", "sha3", - "solver-storage 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", - "thiserror 2.0.18", + "solver-settlement", + "solver-storage", + "solver-types", "tokio", "toml 0.9.12+spec-1.1.0", "tracing", @@ -5597,26 +5573,7 @@ dependencies = [ "redis", "serde", "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server)", - "thiserror 2.0.18", - "tokio", - "toml 0.9.12+spec-1.1.0", - "tracing", - "uuid", -] - -[[package]] -name = "solver-storage" -version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" -dependencies = [ - "async-trait", - "chrono", - "fs2", - "redis", - "serde", - "serde_json", - "solver-types 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "solver-types", "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", @@ -5652,34 +5609,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "solver-types" -version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" -dependencies = [ - "alloy-consensus", - "alloy-contract", - "alloy-primitives", - "alloy-provider", - "alloy-rpc-client", - "alloy-rpc-types", - "alloy-signer", - "alloy-sol-types", - "alloy-transport", - "async-trait", - "axum", - "bytes", - "hex", - "rust_decimal", - "serde", - "serde_json", - "thiserror 2.0.18", - "tokio", - "toml 0.9.12+spec-1.1.0", - "tracing", - "zeroize", -] - [[package]] name = "spki" version = "0.7.3" diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index e0bc20d..c65a4c9 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -27,7 +27,7 @@ solver-runtime = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement", + "solver-settlement-impls", "solver-storage", "solver-types", ] @@ -100,7 +100,7 @@ solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", branch = solver-order = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } solver-service = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-settlement = { path = "../solver-settlement", optional = true } +solver-settlement-impls = { path = "../solver-settlement", optional = true } solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["oif-interfaces"], optional = true } @@ -109,6 +109,6 @@ assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" tempfile = "3.8" -solver-settlement = { path = "../solver-settlement", features = ["testing"] } +solver-settlement-impls = { path = "../solver-settlement", features = ["testing"] } solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["testing"] } diff --git a/solver-cli/src/solver/engine.rs b/solver-cli/src/solver/engine.rs index e5cc1b5..18045e5 100644 --- a/solver-cli/src/solver/engine.rs +++ b/solver-cli/src/solver/engine.rs @@ -27,7 +27,7 @@ async fn run_solver_from_config_impl(config_path: &Path) -> Result<()> { use solver_config::Config; use solver_core::SolverFactories; - use solver_settlement::centralized::create_settlement; + use solver_settlement_impls::centralized::create_settlement; tracing::info!("Loading config from {}", config_path.display()); @@ -70,14 +70,14 @@ async fn run_solver_from_config_impl(config_path: &Path) -> Result<()> { pricing_factories.insert(name.to_string(), factory); } - let mut settlement_factories: HashMap = + let mut settlement_factories: HashMap = HashMap::new(); - for (name, factory) in solver_settlement::get_all_implementations() { + for (name, factory) in solver_settlement_impls::get_all_implementations() { settlement_factories.insert(name.to_string(), factory); } settlement_factories.insert( "centralized".to_string(), - create_settlement as solver_settlement::SettlementFactory, + create_settlement as solver_settlement_impls::SettlementFactory, ); let mut strategy_factories = HashMap::new(); diff --git a/solver-settlement/Cargo.toml b/solver-settlement/Cargo.toml index 3931b4c..204b3e6 100644 --- a/solver-settlement/Cargo.toml +++ b/solver-settlement/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "solver-settlement" +name = "solver-settlement-impls" version = "0.1.0" edition = "2024" @@ -14,9 +14,9 @@ alloy-sol-types = { version = "1.0.37" } async-trait = "0.1" serde_json = "1.0" sha3 = "0.10" -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc" } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["oif-interfaces"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server" } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["oif-interfaces"] } tokio = { version = "1", features = ["rt"] } toml = "0.9" tracing = "0.1" -upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc" } +upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server" } From 66ae2a7b130ef07b8c0c7abe86fac0bdc2eedb39 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Thu, 5 Mar 2026 17:24:57 +0100 Subject: [PATCH 05/12] quickfix --- Cargo.lock | 103 ++++++++++++++++++++++++++--------------------------- 1 file changed, 51 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a0a6397..2eba4a1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,9 +59,9 @@ dependencies = [ [[package]] name = "alloy-chains" -version = "0.2.31" +version = "0.2.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9d22005bf31b018f31ef9ecadb5d2c39cf4f6acc8db0456f72c815f3d7f757" +checksum = "90f374d3c6d729268bbe2d0e0ff992bb97898b2df756691a62ee1d5f0506bc39" dependencies = [ "alloy-primitives", "num_enum", @@ -828,12 +828,13 @@ dependencies = [ [[package]] name = "alloy-trie" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +checksum = "4d7fd448ab0a017de542de1dcca7a58e7019fe0e7a34ed3f9543ebddf6aceffa" dependencies = [ "alloy-primitives", "alloy-rlp", + "arrayvec", "derive_more", "nybbles", "serde", @@ -1122,12 +1123,15 @@ name = "arrayvec" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] [[package]] name = "assert_cmd" -version = "2.2.0" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a686bbee5efb88a82df0621b236e74d925f470e5445d3220a5648b892ec99c9" +checksum = "9c5bcfa8749ac45dd12cb11055aeeb6b27a3895560d60d71e3c23bf979e60514" dependencies = [ "anstyle", "bstr", @@ -1890,9 +1894,9 @@ dependencies = [ [[package]] name = "c-kzg" -version = "2.1.7" +version = "2.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +checksum = "1a0f582957c24870b7bfd12bf562c40b4734b533cafbaf8ded31d6d85f462c01" dependencies = [ "blst", "cc", @@ -3111,7 +3115,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -3489,9 +3493,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.183" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libm" @@ -3769,9 +3773,9 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl" -version = "0.10.76" +version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags", "cfg-if", @@ -3801,9 +3805,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" -version = "0.9.112" +version = "0.9.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" dependencies = [ "cc", "libc", @@ -4235,7 +4239,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.37", - "socket2 0.6.3", + "socket2 0.6.2", "thiserror 2.0.18", "tokio", "tracing", @@ -4244,9 +4248,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.14" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ "bytes", "getrandom 0.3.4", @@ -4272,7 +4276,7 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.3", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] @@ -4428,7 +4432,7 @@ dependencies = [ "pin-project-lite", "ryu", "sha1_smol", - "socket2 0.6.3", + "socket2 0.6.2", "tokio", "tokio-util", "url", @@ -4815,9 +4819,9 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "schannel" -version = "0.1.29" +version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" dependencies = [ "windows-sys 0.61.2", ] @@ -5249,28 +5253,25 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-consensus", "alloy-network", "alloy-primitives", "alloy-signer", - "alloy-signer-aws", "alloy-signer-local", "async-trait", - "aws-config", - "aws-sdk-kms", "hex", "solver-types", "thiserror 2.0.18", @@ -5290,8 +5291,6 @@ dependencies = [ "anyhow", "assert_cmd", "async-trait", - "aws-config", - "aws-sdk-kms", "chrono", "clap", "colored", @@ -5334,7 +5333,7 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "dotenvy", "regex", @@ -5349,7 +5348,7 @@ dependencies = [ [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "chrono", @@ -5376,7 +5375,7 @@ dependencies = [ [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-consensus", "alloy-network", @@ -5405,7 +5404,7 @@ dependencies = [ [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5435,7 +5434,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5455,7 +5454,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "async-trait", @@ -5473,7 +5472,7 @@ dependencies = [ [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5520,7 +5519,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5564,7 +5563,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "async-trait", "chrono", @@ -5584,7 +5583,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#9eebeeb8202e644529010a7112671b480bfd8cd7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" dependencies = [ "alloy-consensus", "alloy-contract", @@ -5747,9 +5746,9 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.27.0" +version = "3.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", "getrandom 0.4.2", @@ -5890,7 +5889,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -6357,9 +6356,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.22.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ "getrandom 0.4.2", "js-sys", @@ -6909,9 +6908,9 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.7.15" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -7069,18 +7068,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.42" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2578b716f8a7a858b7f02d5bd870c14bf4ddbbcf3a4c05414ba6503640505e3" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.42" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e6cc098ea4d3bd6246687de65af3f920c430e236bee1e3bf2e441463f08a02f" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", From e0297ac76990a46898930a82962a85e35edc5009 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 12:48:21 +0100 Subject: [PATCH 06/12] clippy and fmt --- Cargo.lock | 5 + solver-cli/Cargo.toml | 2 +- solver-cli/src/commands/ofac.rs | 278 ++++++++++++++-------------- solver-cli/src/solver/config_gen.rs | 16 -- 4 files changed, 145 insertions(+), 156 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2eba4a1..de62b15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5270,8 +5270,11 @@ dependencies = [ "alloy-network", "alloy-primitives", "alloy-signer", + "alloy-signer-aws", "alloy-signer-local", "async-trait", + "aws-config", + "aws-sdk-kms", "hex", "solver-types", "thiserror 2.0.18", @@ -5291,6 +5294,8 @@ dependencies = [ "anyhow", "assert_cmd", "async-trait", + "aws-config", + "aws-sdk-kms", "chrono", "clap", "colored", diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index c65a4c9..4f2cc28 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -108,7 +108,7 @@ solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jo assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" -tempfile = "3.8" solver-settlement-impls = { path = "../solver-settlement", features = ["testing"] } solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["testing"] } +tempfile = "3.8" diff --git a/solver-cli/src/commands/ofac.rs b/solver-cli/src/commands/ofac.rs index ad1a618..9e1b6c2 100644 --- a/solver-cli/src/commands/ofac.rs +++ b/solver-cli/src/commands/ofac.rs @@ -9,84 +9,84 @@ use crate::OutputFormat; #[derive(Subcommand)] pub enum OfacCommand { - /// Update the OFAC sanctions list from the official SDN source - Update { - /// Project directory - #[arg(long)] - dir: Option, - }, + /// Update the OFAC sanctions list from the official SDN source + Update { + /// Project directory + #[arg(long)] + dir: Option, + }, } impl OfacCommand { - pub async fn run(self, output: OutputFormat) -> Result<()> { - match self { - OfacCommand::Update { dir } => Self::update(dir, output).await, - } - } - - async fn update(dir: Option, output: OutputFormat) -> Result<()> { - let out = OutputFormatter::new(output); - let project_dir = dir.unwrap_or_else(|| env::current_dir().unwrap()); - let config_dir = project_dir.join(".config"); - - out.header("Updating OFAC Sanctions List"); - - let urls = [ - "https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML", - "https://www.treasury.gov/ofac/downloads/sdn.xml", - ]; - - let client = reqwest::Client::builder() - .timeout(std::time::Duration::from_secs(120)) - .user_agent("Mozilla/5.0 (compatible; solver-cli/1.0; +https://github.com/celestiaorg)") - .build()?; - - let mut last_status = None; - let mut xml_opt: Option = None; - - for url in &urls { - print_info(&format!("Source: {}", url)); - let response = client.get(*url).send().await?; - if response.status().is_success() { - xml_opt = Some(response.text().await?); - break; - } - print_info(&format!(" → HTTP {} — trying next URL", response.status())); - last_status = Some(response.status()); - } - - let xml = xml_opt.ok_or_else(|| { - anyhow::anyhow!( - "Failed to fetch OFAC SDN list: HTTP {}", - last_status - .map(|s| s.to_string()) - .unwrap_or_else(|| "unknown".into()) - ) - })?; - - print_info(&format!("Downloaded {} bytes", xml.len())); - - let addresses = parse_eth_addresses_from_sdn_xml(&xml); - let count = addresses.len(); - print_info(&format!("Found {} Ethereum addresses", count)); - - tokio::fs::create_dir_all(&config_dir).await?; - let ofac_path = config_dir.join("ofac.json"); - let json = serde_json::to_string_pretty(&addresses)?; - tokio::fs::write(&ofac_path, &json).await?; - - print_success(&format!("Saved to: {}", ofac_path.display())); - print_info("Run 'solver-cli configure' to include the list in the solver config."); - - if out.is_json() { - out.json(&serde_json::json!({ - "count": count, - "path": ofac_path.display().to_string(), - }))?; - } - - Ok(()) - } + pub async fn run(self, output: OutputFormat) -> Result<()> { + match self { + OfacCommand::Update { dir } => Self::update(dir, output).await, + } + } + + async fn update(dir: Option, output: OutputFormat) -> Result<()> { + let out = OutputFormatter::new(output); + let project_dir = dir.unwrap_or_else(|| env::current_dir().unwrap()); + let config_dir = project_dir.join(".config"); + + out.header("Updating OFAC Sanctions List"); + + let urls = [ + "https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML", + "https://www.treasury.gov/ofac/downloads/sdn.xml", + ]; + + let client = reqwest::Client::builder() + .timeout(std::time::Duration::from_secs(120)) + .user_agent("Mozilla/5.0 (compatible; solver-cli/1.0; +https://github.com/celestiaorg)") + .build()?; + + let mut last_status = None; + let mut xml_opt: Option = None; + + for url in &urls { + print_info(&format!("Source: {}", url)); + let response = client.get(*url).send().await?; + if response.status().is_success() { + xml_opt = Some(response.text().await?); + break; + } + print_info(&format!(" → HTTP {} — trying next URL", response.status())); + last_status = Some(response.status()); + } + + let xml = xml_opt.ok_or_else(|| { + anyhow::anyhow!( + "Failed to fetch OFAC SDN list: HTTP {}", + last_status + .map(|s| s.to_string()) + .unwrap_or_else(|| "unknown".into()) + ) + })?; + + print_info(&format!("Downloaded {} bytes", xml.len())); + + let addresses = parse_eth_addresses_from_sdn_xml(&xml); + let count = addresses.len(); + print_info(&format!("Found {} Ethereum addresses", count)); + + tokio::fs::create_dir_all(&config_dir).await?; + let ofac_path = config_dir.join("ofac.json"); + let json = serde_json::to_string_pretty(&addresses)?; + tokio::fs::write(&ofac_path, &json).await?; + + print_success(&format!("Saved to: {}", ofac_path.display())); + print_info("Run 'solver-cli configure' to include the list in the solver config."); + + if out.is_json() { + out.json(&serde_json::json!({ + "count": count, + "path": ofac_path.display().to_string(), + }))?; + } + + Ok(()) + } } /// Parse Ethereum addresses from an OFAC SDN XML document. @@ -95,59 +95,59 @@ impl OfacCommand { /// (ETH, ERC-20 tokens like USDC/USDT, etc.) and extracts any `` /// that matches Ethereum address format (0x + 40 hex digits). pub fn parse_eth_addresses_from_sdn_xml(xml: &str) -> Vec { - let mut addresses: HashSet = HashSet::new(); - - // Each ... block may contain one crypto address entry. - for block in xml.split("").skip(1) { - let block = match block.split("").next() { - Some(b) => b, - None => continue, - }; - - if !block.contains("Digital Currency Address") { - continue; - } - - let start = match block.find("") { - Some(i) => i + "".len(), - None => continue, - }; - let end = match block.find("") { - Some(i) => i, - None => continue, - }; - - if end <= start { - continue; - } - - let raw = block[start..end].trim(); - - // Normalise to lowercase with 0x prefix - let addr = if raw.starts_with("0x") || raw.starts_with("0X") { - raw.to_lowercase() - } else { - format!("0x{}", raw.to_lowercase()) - }; - - // Ethereum addresses are 42 chars: "0x" + 40 hex digits - if addr.len() == 42 && addr[2..].chars().all(|c| c.is_ascii_hexdigit()) { - addresses.insert(addr); - } - } - - let mut sorted: Vec = addresses.into_iter().collect(); - sorted.sort(); - sorted + let mut addresses: HashSet = HashSet::new(); + + // Each ... block may contain one crypto address entry. + for block in xml.split("").skip(1) { + let block = match block.split("").next() { + Some(b) => b, + None => continue, + }; + + if !block.contains("Digital Currency Address") { + continue; + } + + let start = match block.find("") { + Some(i) => i + "".len(), + None => continue, + }; + let end = match block.find("") { + Some(i) => i, + None => continue, + }; + + if end <= start { + continue; + } + + let raw = block[start..end].trim(); + + // Normalise to lowercase with 0x prefix + let addr = if raw.starts_with("0x") || raw.starts_with("0X") { + raw.to_lowercase() + } else { + format!("0x{}", raw.to_lowercase()) + }; + + // Ethereum addresses are 42 chars: "0x" + 40 hex digits + if addr.len() == 42 && addr[2..].chars().all(|c| c.is_ascii_hexdigit()) { + addresses.insert(addr); + } + } + + let mut sorted: Vec = addresses.into_iter().collect(); + sorted.sort(); + sorted } #[cfg(test)] mod tests { - use super::*; + use super::*; - #[test] - fn test_parse_eth_addresses() { - let xml = r#" + #[test] + fn test_parse_eth_addresses() { + let xml = r#" Digital Currency Address - ETH @@ -171,20 +171,20 @@ mod tests { "#; - let addrs = parse_eth_addresses_from_sdn_xml(xml); - assert_eq!(addrs.len(), 4); - assert!(addrs.contains(&"0xabcdef1234567890abcdef1234567890abcdef12".to_string())); - assert!(addrs.contains(&"0x0000000000000000000000000000000000000001".to_string())); - assert!(addrs.contains(&"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_string())); - assert!(addrs.contains(&"0x1111111111111111111111111111111111111111".to_string())); - // Bitcoin address must not be included - assert!(!addrs.iter().any(|a| a.contains("1A1z"))); - } - - #[test] - fn test_parse_no_eth_addresses() { - let xml = r#""#; - let addrs = parse_eth_addresses_from_sdn_xml(xml); - assert!(addrs.is_empty()); - } + let addrs = parse_eth_addresses_from_sdn_xml(xml); + assert_eq!(addrs.len(), 4); + assert!(addrs.contains(&"0xabcdef1234567890abcdef1234567890abcdef12".to_string())); + assert!(addrs.contains(&"0x0000000000000000000000000000000000000001".to_string())); + assert!(addrs.contains(&"0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef".to_string())); + assert!(addrs.contains(&"0x1111111111111111111111111111111111111111".to_string())); + // Bitcoin address must not be included + assert!(!addrs.iter().any(|a| a.contains("1A1z"))); + } + + #[test] + fn test_parse_no_eth_addresses() { + let xml = r#""#; + let addrs = parse_eth_addresses_from_sdn_xml(xml); + assert!(addrs.is_empty()); + } } diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index 4b8e76f..2d534d5 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -101,21 +101,6 @@ decimals = {} output_oracles.push(format!("{} = [\"{}\"]", chain.chain_id, oracle)); } - // Build mock price pairs from all configured tokens - let mut price_symbols: Vec = state - .chains - .values() - .flat_map(|c| c.tokens.keys().cloned()) - .collect::>() - .into_iter() - .collect(); - price_symbols.sort(); - let mock_prices = price_symbols - .iter() - .map(|sym| format!("\"{}/USD\" = \"1.0\"", sym)) - .collect::>() - .join("\n"); - // Include OFAC list path if the file exists let ofac_line = if std::path::Path::new(".config/ofac.json").exists() { "ofac_list = \".config/ofac.json\"\n".to_string() @@ -123,7 +108,6 @@ decimals = {} String::new() }; - // Build routes (all-to-all) let mut routes = Vec::new(); for &from_id in &chain_ids { From 90ff6e0cf4d98720384f38a5031e133bddec5c63 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 12:53:47 +0100 Subject: [PATCH 07/12] update API --- Cargo.lock | 40 +++++++++++++--------------- solver-cli/Cargo.toml | 22 +++++++-------- solver-settlement/Cargo.toml | 6 ++--- solver-settlement/src/centralized.rs | 26 ++++++++++++++---- 4 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de62b15..e37aa9b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5264,7 +5264,7 @@ dependencies = [ [[package]] name = "solver-account" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-consensus", "alloy-network", @@ -5276,10 +5276,11 @@ dependencies = [ "aws-config", "aws-sdk-kms", "hex", + "serde", + "serde_json", "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", ] [[package]] @@ -5338,24 +5339,27 @@ dependencies = [ [[package]] name = "solver-config" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "dotenvy", "regex", "rust_decimal", "serde", + "serde_json", "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", ] [[package]] name = "solver-core" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-primitives", + "alloy-provider", + "alloy-rpc-types", + "alloy-sol-types", "chrono", "lru", "once_cell", @@ -5373,14 +5377,13 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", ] [[package]] name = "solver-delivery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-consensus", "alloy-network", @@ -5402,14 +5405,13 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", ] [[package]] name = "solver-discovery" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5430,7 +5432,6 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tower", "tower-http", "tracing", @@ -5439,7 +5440,7 @@ dependencies = [ [[package]] name = "solver-order" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5451,7 +5452,6 @@ dependencies = [ "serde_json", "solver-types", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", "tracing", "uuid", ] @@ -5459,7 +5459,7 @@ dependencies = [ [[package]] name = "solver-pricing" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-primitives", "async-trait", @@ -5470,14 +5470,13 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", ] [[package]] name = "solver-service" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5513,7 +5512,6 @@ dependencies = [ "subtle", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tower", "tower-http", "tracing", @@ -5524,7 +5522,7 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-primitives", "alloy-provider", @@ -5538,11 +5536,11 @@ dependencies = [ "serde", "serde_json", "sha3", + "solver-delivery", "solver-storage", "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", ] @@ -5568,7 +5566,7 @@ dependencies = [ [[package]] name = "solver-storage" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "async-trait", "chrono", @@ -5580,7 +5578,6 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", "uuid", ] @@ -5588,7 +5585,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Ffix-expose-api-server#7fa3e779cdbaccf94609185d6a7fe65880d791d7" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-consensus", "alloy-contract", @@ -5608,7 +5605,6 @@ dependencies = [ "serde_json", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", "zeroize", ] diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index 4f2cc28..e12ec30 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -92,23 +92,23 @@ futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } sha3 = { version = "0.10", optional = true } -solver-account = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-config = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-core = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-order = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-service = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } +solver-account = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-config = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-core = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-delivery = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-discovery = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-order = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-pricing = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-service = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } solver-settlement-impls = { path = "../solver-settlement", optional = true } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", optional = true } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["oif-interfaces"], optional = true } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", optional = true } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", features = ["oif-interfaces"], optional = true } [dev-dependencies] assert_cmd = "2.0" mockall = "0.13" predicates = "3.0" solver-settlement-impls = { path = "../solver-settlement", features = ["testing"] } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["testing"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", features = ["testing"] } tempfile = "3.8" diff --git a/solver-settlement/Cargo.toml b/solver-settlement/Cargo.toml index 204b3e6..2ed9913 100644 --- a/solver-settlement/Cargo.toml +++ b/solver-settlement/Cargo.toml @@ -14,9 +14,9 @@ alloy-sol-types = { version = "1.0.37" } async-trait = "0.1" serde_json = "1.0" sha3 = "0.10" -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server" } -solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server", features = ["oif-interfaces"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist" } +solver-types = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", features = ["oif-interfaces"] } tokio = { version = "1", features = ["rt"] } toml = "0.9" tracing = "0.1" -upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/fix-expose-api-server" } +upstream-solver-settlement = { package = "solver-settlement", git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist" } diff --git a/solver-settlement/src/centralized.rs b/solver-settlement/src/centralized.rs index a8c06c7..b986a8b 100644 --- a/solver-settlement/src/centralized.rs +++ b/solver-settlement/src/centralized.rs @@ -265,14 +265,30 @@ impl CentralizedSettlement { pub struct CentralizedSettlementSchema; +fn toml_to_json(value: &toml::Value) -> serde_json::Value { + match value { + toml::Value::String(s) => serde_json::Value::String(s.clone()), + toml::Value::Integer(i) => serde_json::json!(*i), + toml::Value::Float(f) => serde_json::json!(*f), + toml::Value::Boolean(b) => serde_json::Value::Bool(*b), + toml::Value::Datetime(d) => serde_json::Value::String(d.to_string()), + toml::Value::Array(a) => serde_json::Value::Array(a.iter().map(toml_to_json).collect()), + toml::Value::Table(t) => { + let map = t.iter().map(|(k, v)| (k.clone(), toml_to_json(v))).collect(); + serde_json::Value::Object(map) + } + } +} + impl CentralizedSettlementSchema { pub fn validate_config(config: &toml::Value) -> Result<(), solver_types::ValidationError> { - CentralizedSettlementSchema.validate(config) + let json = toml_to_json(config); + CentralizedSettlementSchema.validate(&json) } } impl ConfigSchema for CentralizedSettlementSchema { - fn validate(&self, config: &toml::Value) -> Result<(), solver_types::ValidationError> { + fn validate(&self, config: &serde_json::Value) -> Result<(), solver_types::ValidationError> { let schema = Schema::new( vec![ Field::new( @@ -298,7 +314,7 @@ impl ConfigSchema for CentralizedSettlementSchema { ), ], ); - schema.validate(config) + schema.validate(&config) } } @@ -531,11 +547,11 @@ impl SettlementInterface for CentralizedSettlement { } pub fn create_settlement( - config: &toml::Value, + config: &serde_json::Value, networks: &NetworksConfig, _storage: Arc, ) -> Result, SettlementError> { - CentralizedSettlementSchema::validate_config(config) + CentralizedSettlementSchema.validate(config) .map_err(|e| SettlementError::ValidationFailed(format!("Invalid configuration: {}", e)))?; let oracle_config = parse_oracle_config(config)?; From b04b20e91cd8361a9250694efc721fab76f53dd1 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 13:00:39 +0100 Subject: [PATCH 08/12] cleanup --- .continue/config.yaml | 17 ----------------- .gitignore | 1 + solver-settlement/src/centralized.rs | 8 ++++++-- 3 files changed, 7 insertions(+), 19 deletions(-) delete mode 100644 .continue/config.yaml diff --git a/.continue/config.yaml b/.continue/config.yaml deleted file mode 100644 index f313691..0000000 --- a/.continue/config.yaml +++ /dev/null @@ -1,17 +0,0 @@ -models: - - name: DeepSeek Chat - provider: ollama - model: deepseek-coder:33b - roles: - - chat - - edit - - - name: DeepSeek Fast - provider: ollama - model: deepseek-coder:6.7b - roles: - - autocomplete - -embeddings: - provider: ollama - model: nomic-embed-text \ No newline at end of file diff --git a/.gitignore b/.gitignore index fc4acaa..4f4c34a 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ config/ .rebalancer .docs/ .DS_Store +.continue .claude .config/ diff --git a/solver-settlement/src/centralized.rs b/solver-settlement/src/centralized.rs index b986a8b..91fd1ef 100644 --- a/solver-settlement/src/centralized.rs +++ b/solver-settlement/src/centralized.rs @@ -274,7 +274,10 @@ fn toml_to_json(value: &toml::Value) -> serde_json::Value { toml::Value::Datetime(d) => serde_json::Value::String(d.to_string()), toml::Value::Array(a) => serde_json::Value::Array(a.iter().map(toml_to_json).collect()), toml::Value::Table(t) => { - let map = t.iter().map(|(k, v)| (k.clone(), toml_to_json(v))).collect(); + let map = t + .iter() + .map(|(k, v)| (k.clone(), toml_to_json(v))) + .collect(); serde_json::Value::Object(map) } } @@ -551,7 +554,8 @@ pub fn create_settlement( networks: &NetworksConfig, _storage: Arc, ) -> Result, SettlementError> { - CentralizedSettlementSchema.validate(config) + CentralizedSettlementSchema + .validate(config) .map_err(|e| SettlementError::ValidationFailed(format!("Invalid configuration: {}", e)))?; let oracle_config = parse_oracle_config(config)?; From 7d8c8f38ae566240213cb76ef8fc8e537cd4c8ae Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 13:02:09 +0100 Subject: [PATCH 09/12] CI --- solver-settlement/src/centralized.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solver-settlement/src/centralized.rs b/solver-settlement/src/centralized.rs index 91fd1ef..838d812 100644 --- a/solver-settlement/src/centralized.rs +++ b/solver-settlement/src/centralized.rs @@ -317,7 +317,7 @@ impl ConfigSchema for CentralizedSettlementSchema { ), ], ); - schema.validate(&config) + schema.validate(config) } } From 41a894d2239d93d83dfd41c7e19955ac916decbe Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 15:20:27 +0100 Subject: [PATCH 10/12] bump aggregator version --- Cargo.lock | 1 + hyperlane/configs/warp-config.yaml | 2 +- hyperlane/hyperlane-addresses.json | 29 ++++ hyperlane/hyperlane-cosmosnative.json | 8 + .../registry/chains/anvil1/addresses.yaml | 14 ++ .../registry/chains/anvil2/addresses.yaml | 14 ++ .../warp_routes/USDC/warp-config-config.yaml | 19 +++ .../warp_routes/USDC/warp-config-deploy.yaml | 15 ++ solver-cli/Cargo.toml | 2 + solver-cli/src/solver/config_gen.rs | 1 + solver-cli/src/solver/engine.rs | 153 +++++++++++++++++- 11 files changed, 253 insertions(+), 5 deletions(-) create mode 100644 hyperlane/hyperlane-addresses.json create mode 100644 hyperlane/hyperlane-cosmosnative.json create mode 100644 hyperlane/registry/chains/anvil1/addresses.yaml create mode 100644 hyperlane/registry/chains/anvil2/addresses.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml create mode 100644 hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml diff --git a/Cargo.lock b/Cargo.lock index e37aa9b..c723e80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5297,6 +5297,7 @@ dependencies = [ "async-trait", "aws-config", "aws-sdk-kms", + "axum", "chrono", "clap", "colored", diff --git a/hyperlane/configs/warp-config.yaml b/hyperlane/configs/warp-config.yaml index 2828ae2..c71dc75 100644 --- a/hyperlane/configs/warp-config.yaml +++ b/hyperlane/configs/warp-config.yaml @@ -3,7 +3,7 @@ # after deploying MockERC20 anvil1: type: collateral - token: "MOCK_USDC_ADDRESS_PLACEHOLDER" + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" name: "USDC" symbol: "USDC" diff --git a/hyperlane/hyperlane-addresses.json b/hyperlane/hyperlane-addresses.json new file mode 100644 index 0000000..64f24e3 --- /dev/null +++ b/hyperlane/hyperlane-addresses.json @@ -0,0 +1,29 @@ +{ + "anvil1": { + "chain_id": 31337, + "domain_id": 131337, + "mock_usdc": "0x5FbDB2315678afecb367f032d93F642f64180aa3", + "warp_token": "0x4A679253410272dd5232B3Ff7cF5dbB88f295319", + "warp_token_type": "collateral", + "mailbox": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0", + "merkle_tree_hook": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", + "validator_announce": "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" + }, + "anvil2": { + "chain_id": 31338, + "domain_id": 31338, + "warp_token": "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44", + "warp_token_type": "synthetic", + "mailbox": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788", + "merkle_tree_hook": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", + "validator_announce": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + }, + "celestiadev": { + "domain_id": 69420, + "synthetic_token": "0x726f757465725f61707000000000000000000000000000020000000000000000", + "mailbox": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "merkle_tree_hook": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "ism": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "default_hook": "0x726f757465725f706f73745f6469737061746368000000000000000000000001" + } +} diff --git a/hyperlane/hyperlane-cosmosnative.json b/hyperlane/hyperlane-cosmosnative.json new file mode 100644 index 0000000..ae2a37b --- /dev/null +++ b/hyperlane/hyperlane-cosmosnative.json @@ -0,0 +1,8 @@ +{ + "ism_id": "0x726f757465725f69736d00000000000000000000000000000000000000000000", + "mailbox_id": "0x68797065726c616e650000000000000000000000000000000000000000000000", + "default_hook_id": "0x726f757465725f706f73745f6469737061746368000000000000000000000001", + "required_hook_id": "0x726f757465725f706f73745f6469737061746368000000030000000000000002", + "collateral_token_id": "0x0000000000000000000000000000000000000000000000000000000000000000", + "synthetic_token_id": "0x726f757465725f61707000000000000000000000000000020000000000000000" +} \ No newline at end of file diff --git a/hyperlane/registry/chains/anvil1/addresses.yaml b/hyperlane/registry/chains/anvil1/addresses.yaml new file mode 100644 index 0000000..86b298a --- /dev/null +++ b/hyperlane/registry/chains/anvil1/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +incrementalDomainRoutingIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +interchainAccountRouter: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" +merkleTreeHook: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +proxyAdmin: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +staticAggregationHookFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +staticAggregationIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +staticMerkleRootMultisigIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootWeightedMultisigIsmFactory: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticMessageIdMultisigIsmFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticMessageIdWeightedMultisigIsmFactory: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318" +testRecipient: "0x3Aa5ebB10DC797CAC828524e59A333d0A371443c" +validatorAnnounce: "0x68B1D87F95878fE05B998F19b66F4baba5De1aed" diff --git a/hyperlane/registry/chains/anvil2/addresses.yaml b/hyperlane/registry/chains/anvil2/addresses.yaml new file mode 100644 index 0000000..ad2a018 --- /dev/null +++ b/hyperlane/registry/chains/anvil2/addresses.yaml @@ -0,0 +1,14 @@ +domainRoutingIsmFactory: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" +incrementalDomainRoutingIsmFactory: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707" +interchainAccountRouter: "0x0B306BF915C4d645ff596e518fAf3F9669b97016" +mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" +merkleTreeHook: "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" +proxyAdmin: "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" +staticAggregationHookFactory: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9" +staticAggregationIsmFactory: "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" +staticMerkleRootMultisigIsmFactory: "0x5FbDB2315678afecb367f032d93F642f64180aa3" +staticMerkleRootWeightedMultisigIsmFactory: "0x0165878A594ca255338adfa4d48449f69242Eb8F" +staticMessageIdMultisigIsmFactory: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512" +staticMessageIdWeightedMultisigIsmFactory: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853" +testRecipient: "0x9A9f2CCfdE556A7E9Ff0848998Aa4a0CFD8863AE" +validatorAnnounce: "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml new file mode 100644 index 0000000..4ad5a1b --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-config.yaml @@ -0,0 +1,19 @@ +# yaml-language-server: $schema=../schema.json +tokens: + - addressOrDenom: "0x4A679253410272dd5232B3Ff7cF5dbB88f295319" + chainName: anvil1 + collateralAddressOrDenom: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + connections: + - token: ethereum|anvil2|0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44 + decimals: 6 + name: USDC + standard: EvmHypCollateral + symbol: USDC + - addressOrDenom: "0x322813Fd9A801c5507c9de605d63CEA4f2CE6c44" + chainName: anvil2 + connections: + - token: ethereum|anvil1|0x4A679253410272dd5232B3Ff7cF5dbB88f295319 + decimals: 6 + name: USDC + standard: EvmHypSynthetic + symbol: USDC diff --git a/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml new file mode 100644 index 0000000..dec3a6a --- /dev/null +++ b/hyperlane/registry/deployments/warp_routes/USDC/warp-config-deploy.yaml @@ -0,0 +1,15 @@ +anvil1: + decimals: 6 + mailbox: "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + token: "0x5FbDB2315678afecb367f032d93F642f64180aa3" + type: collateral +anvil2: + decimals: 6 + mailbox: "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + name: USDC + owner: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266" + symbol: USDC + type: synthetic diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index e12ec30..9b4ecba 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -12,6 +12,7 @@ path = "src/main.rs" [features] default = ["solver-runtime"] solver-runtime = [ + "dep:axum", "solver-config", "solver-service", "alloy-primitives", @@ -85,6 +86,7 @@ uuid = { version = "1.6", features = ["v4", "serde"] } # Unix process management [target.'cfg(unix)'.dependencies] alloy-primitives = { version = "1.0.37", features = ["std", "serde"], optional = true } +axum = { version = "0.8", optional = true } alloy-provider = { version = "1.0", optional = true } alloy-rpc-types = { version = "1.0", optional = true } alloy-sol-types = { version = "1.0.37", optional = true } diff --git a/solver-cli/src/solver/config_gen.rs b/solver-cli/src/solver/config_gen.rs index 2d534d5..46405e9 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -242,6 +242,7 @@ fallbacks = ["coingecko"] # SETTLEMENT # ============================================================================ [settlement] +primary = "centralized" settlement_poll_interval_seconds = 12 [settlement.implementations.centralized] diff --git a/solver-cli/src/solver/engine.rs b/solver-cli/src/solver/engine.rs index 18045e5..8f8a384 100644 --- a/solver-cli/src/solver/engine.rs +++ b/solver-cli/src/solver/engine.rs @@ -108,17 +108,33 @@ async fn run_solver_from_config_impl(config_path: &Path) -> Result<()> { let api_enabled = config.api.as_ref().is_some_and(|api| api.enabled); if api_enabled { - let api_config = config.api.as_ref().unwrap().clone(); + let mut api_config = config.api.as_ref().unwrap().clone(); let api_solver = Arc::clone(&solver); + // The external port that clients (aggregator) connect to + let external_host = api_config.host.clone(); + let external_port = api_config.port; + + // Run the upstream solver API on an internal port. A lightweight proxy + // on the external port strips fields the aggregator doesn't understand + // (e.g. `settlementName` added in newer solver versions). + let internal_port = external_port + 1000; + api_config.port = internal_port; + tracing::info!( - "Starting solver with API server on {}:{}", - api_config.host, - api_config.port + "Starting solver API on internal port {} with proxy on {}:{}", + internal_port, + external_host, + external_port ); let solver_task = solver.run(); let api_task = solver_service::server::start_server(api_config, api_solver); + let proxy_task = start_compat_proxy( + external_host, + external_port, + format!("http://127.0.0.1:{internal_port}"), + ); tokio::select! { result = solver_task => { @@ -129,6 +145,10 @@ async fn run_solver_from_config_impl(config_path: &Path) -> Result<()> { tracing::info!("API server finished"); result.map_err(|e| anyhow::anyhow!("{}", e))?; } + result = proxy_task => { + tracing::info!("Proxy finished"); + result?; + } } } else { tracing::info!("Starting solver (no API server)"); @@ -138,3 +158,128 @@ async fn run_solver_from_config_impl(config_path: &Path) -> Result<()> { tracing::info!("Solver stopped"); Ok(()) } + +/// Compatibility proxy that forwards requests to the internal solver API and +/// strips fields from quote responses that the aggregator doesn't understand. +#[cfg(feature = "solver-runtime")] +async fn start_compat_proxy( + host: String, + port: u16, + upstream: String, +) -> Result<()> { + use axum::{ + body::Body, + extract::State, + http::{HeaderMap, Method, Uri}, + response::{IntoResponse, Response}, + routing::any, + Router, + }; + use tokio::net::TcpListener; + + #[derive(Clone)] + struct ProxyState { + client: reqwest::Client, + upstream: String, + } + + async fn proxy_handler( + State(state): State, + method: Method, + uri: Uri, + headers: HeaderMap, + body: Body, + ) -> Response { + let url = format!("{}{}", state.upstream, uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")); + let body_bytes = match axum::body::to_bytes(body, 10 * 1024 * 1024).await { + Ok(b) => b, + Err(e) => { + return (axum::http::StatusCode::BAD_REQUEST, format!("body error: {e}")).into_response(); + } + }; + + let mut req = state.client.request(method.clone(), &url); + for (key, val) in headers.iter() { + if key != "host" { + req = req.header(key, val); + } + } + req = req.body(body_bytes); + + let resp = match req.send().await { + Ok(r) => r, + Err(e) => { + return (axum::http::StatusCode::BAD_GATEWAY, format!("upstream error: {e}")).into_response(); + } + }; + + let status = resp.status(); + let resp_headers = resp.headers().clone(); + let resp_bytes = resp.bytes().await.unwrap_or_default(); + + // Strip `settlementName` from quote responses so the aggregator + // (which uses deny_unknown_fields) can parse them. + let is_quotes = uri.path().contains("/quotes"); + let final_bytes = if is_quotes { + strip_settlement_name(&resp_bytes) + } else { + resp_bytes.to_vec() + }; + + let mut response = (status, final_bytes).into_response(); + for (key, val) in resp_headers.iter() { + if key != "content-length" && key != "transfer-encoding" { + response.headers_mut().insert(key.clone(), val.clone()); + } + } + response + } + + let state = ProxyState { + client: reqwest::Client::new(), + upstream, + }; + + let app = Router::new() + .route("/{*path}", any(proxy_handler)) + .route("/", any(proxy_handler)) + .with_state(state); + + let addr = format!("{host}:{port}"); + let listener = TcpListener::bind(&addr).await?; + tracing::info!("Compatibility proxy listening on {addr}"); + + axum::serve(listener, app) + .await + .map_err(|e| anyhow::anyhow!("proxy error: {e}")) +} + +/// Remove `settlementName` keys from JSON bytes. +#[cfg(feature = "solver-runtime")] +fn strip_settlement_name(bytes: &[u8]) -> Vec { + match serde_json::from_slice::(bytes) { + Ok(mut val) => { + remove_key_recursive(&mut val, "settlementName"); + serde_json::to_vec(&val).unwrap_or_else(|_| bytes.to_vec()) + } + Err(_) => bytes.to_vec(), + } +} + +#[cfg(feature = "solver-runtime")] +fn remove_key_recursive(val: &mut serde_json::Value, key: &str) { + match val { + serde_json::Value::Object(map) => { + map.remove(key); + for v in map.values_mut() { + remove_key_recursive(v, key); + } + } + serde_json::Value::Array(arr) => { + for v in arr.iter_mut() { + remove_key_recursive(v, key); + } + } + _ => {} + } +} From ca4cf7450b866794194862bbd8a3cff187491324 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 15:28:57 +0100 Subject: [PATCH 11/12] visualize failure reason --- frontend/server.js | 27 ++++++++++++++++++++++++++- frontend/src/App.tsx | 5 +++++ frontend/src/api.ts | 1 + 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/frontend/server.js b/frontend/server.js index df3eb9a..32ba167 100644 --- a/frontend/server.js +++ b/frontend/server.js @@ -13,8 +13,12 @@ const ROOT = resolve(__dirname, '..'); config({ path: resolve(ROOT, '.env') }); const AGGREGATOR_URL = process.env.AGGREGATOR_URL || 'http://localhost:4000'; +const SOLVER_URL = process.env.SOLVER_URL || 'http://localhost:5001'; const PORT = process.env.BACKEND_PORT || 3001; +// Track order creation times to detect solver rejections +const orderCreatedAt = new Map(); + // ── Helpers ────────────────────────────────────────────────────────────────── function readState() { @@ -587,18 +591,39 @@ app.post('/api/order/submit', async (req, res) => { const data = await response.json(); console.log(`[order/submit] aggregator response: ${response.status}`, JSON.stringify(data).slice(0, 200)); if (!response.ok) throw new Error(data.message || data.error || JSON.stringify(data)); + if (data.orderId) orderCreatedAt.set(data.orderId, Date.now()); res.json(data); } catch (err) { res.status(500).json({ error: err.message }); } }); -// Order status +// Order status — checks solver directly when aggregator status is stuck on 'created' app.get('/api/order/:id', async (req, res) => { try { const response = await fetch(`${AGGREGATOR_URL}/api/v1/orders/${req.params.id}`); const data = await response.json(); if (!response.ok) throw new Error(data.message || data.error || JSON.stringify(data)); + + // If aggregator still says 'created' after a grace period, check the solver directly. + // The solver drops orders it rejects (e.g. unprofitable), returning 400 NOT_FOUND. + const createdTs = orderCreatedAt.get(req.params.id); + if (data.status === 'created' && createdTs && Date.now() - createdTs > 5_000) { + try { + const solverResp = await fetch(`${SOLVER_URL}/api/v1/orders/${req.params.id}`); + if (!solverResp.ok) { + const solverData = await solverResp.json().catch(() => ({})); + console.log(`[order/${req.params.id.slice(0,8)}] solver rejected: ${solverData.error || solverResp.status}`); + orderCreatedAt.delete(req.params.id); + return res.json({ + ...data, + status: 'failed', + failureReason: 'Order rejected by solver — likely insufficient profit margin for gas costs. Try a larger amount.', + }); + } + } catch { /* solver unreachable, keep aggregator status */ } + } + res.json(data); } catch (err) { res.status(500).json({ error: err.message }); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 1a66fcc..e045701 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -691,6 +691,11 @@ export default function App() { )} + {fail && (orderStatus as any).failureReason && ( +

+ {(orderStatus as any).failureReason} +

+ )} ) diff --git a/frontend/src/api.ts b/frontend/src/api.ts index 02cdbcf..dcce7b2 100644 --- a/frontend/src/api.ts +++ b/frontend/src/api.ts @@ -80,6 +80,7 @@ export interface OrderStatus { destinationChainId?: string recipient?: string } + failureReason?: string } export interface HealthStatus { From 2afac0e7e3be956f1e8d36bba9ac17f8781d6134 Mon Sep 17 00:00:00 2001 From: jonas089 Date: Mon, 16 Mar 2026 15:51:49 +0100 Subject: [PATCH 12/12] sort workspace --- solver-cli/Cargo.toml | 2 +- solver-cli/src/solver/engine.rs | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/solver-cli/Cargo.toml b/solver-cli/Cargo.toml index 9b4ecba..940fc92 100644 --- a/solver-cli/Cargo.toml +++ b/solver-cli/Cargo.toml @@ -86,10 +86,10 @@ uuid = { version = "1.6", features = ["v4", "serde"] } # Unix process management [target.'cfg(unix)'.dependencies] alloy-primitives = { version = "1.0.37", features = ["std", "serde"], optional = true } -axum = { version = "0.8", optional = true } alloy-provider = { version = "1.0", optional = true } alloy-rpc-types = { version = "1.0", optional = true } alloy-sol-types = { version = "1.0.37", optional = true } +axum = { version = "0.8", optional = true } futures = { version = "0.3", optional = true } nix = { version = "0.28", features = ["signal", "process"] } rebalancer = { path = "../rebalancer" } diff --git a/solver-cli/src/solver/engine.rs b/solver-cli/src/solver/engine.rs index 8f8a384..618ba3c 100644 --- a/solver-cli/src/solver/engine.rs +++ b/solver-cli/src/solver/engine.rs @@ -162,11 +162,7 @@ async fn run_solver_from_config_impl(config_path: &Path) -> Result<()> { /// Compatibility proxy that forwards requests to the internal solver API and /// strips fields from quote responses that the aggregator doesn't understand. #[cfg(feature = "solver-runtime")] -async fn start_compat_proxy( - host: String, - port: u16, - upstream: String, -) -> Result<()> { +async fn start_compat_proxy(host: String, port: u16, upstream: String) -> Result<()> { use axum::{ body::Body, extract::State, @@ -190,11 +186,19 @@ async fn start_compat_proxy( headers: HeaderMap, body: Body, ) -> Response { - let url = format!("{}{}", state.upstream, uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/")); + let url = format!( + "{}{}", + state.upstream, + uri.path_and_query().map(|pq| pq.as_str()).unwrap_or("/") + ); let body_bytes = match axum::body::to_bytes(body, 10 * 1024 * 1024).await { Ok(b) => b, Err(e) => { - return (axum::http::StatusCode::BAD_REQUEST, format!("body error: {e}")).into_response(); + return ( + axum::http::StatusCode::BAD_REQUEST, + format!("body error: {e}"), + ) + .into_response(); } }; @@ -209,7 +213,11 @@ async fn start_compat_proxy( let resp = match req.send().await { Ok(r) => r, Err(e) => { - return (axum::http::StatusCode::BAD_GATEWAY, format!("upstream error: {e}")).into_response(); + return ( + axum::http::StatusCode::BAD_GATEWAY, + format!("upstream error: {e}"), + ) + .into_response(); } };