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/Cargo.lock b/Cargo.lock index bca0fb1..c723e80 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,18 +5253,18 @@ 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?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-consensus", "alloy-network", @@ -5272,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]] @@ -5292,6 +5297,7 @@ dependencies = [ "async-trait", "aws-config", "aws-sdk-kms", + "axum", "chrono", "clap", "colored", @@ -5319,7 +5325,7 @@ dependencies = [ "solver-order", "solver-pricing", "solver-service", - "solver-settlement 0.1.0", + "solver-settlement-impls", "solver-storage", "solver-types", "tempfile", @@ -5334,24 +5340,27 @@ 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?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?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +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", @@ -5364,19 +5373,18 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "solver-settlement", "solver-storage", "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?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-consensus", "alloy-network", @@ -5398,14 +5406,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?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-contract", "alloy-primitives", @@ -5426,7 +5433,6 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tower", "tower-http", "tracing", @@ -5435,7 +5441,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?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -5447,7 +5453,6 @@ dependencies = [ "serde_json", "solver-types", "thiserror 2.0.18", - "toml 0.9.12+spec-1.1.0", "tracing", "uuid", ] @@ -5455,7 +5460,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?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-primitives", "async-trait", @@ -5466,14 +5471,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?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-primitives", "alloy-signer", @@ -5503,13 +5507,12 @@ dependencies = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "solver-settlement", "solver-storage", "solver-types", "subtle", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tower", "tower-http", "tracing", @@ -5520,42 +5523,42 @@ dependencies = [ [[package]] name = "solver-settlement" version = "0.1.0" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" 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-settlement 0.1.0 (git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc)", + "solver-delivery", "solver-storage", "solver-types", + "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", ] [[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-settlement", "solver-storage", "solver-types", - "thiserror 2.0.18", "tokio", "toml 0.9.12+spec-1.1.0", "tracing", @@ -5564,7 +5567,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?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "async-trait", "chrono", @@ -5576,7 +5579,6 @@ dependencies = [ "solver-types", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", "uuid", ] @@ -5584,7 +5586,7 @@ dependencies = [ [[package]] name = "solver-types" version = "0.1.0" -source = "git+https://github.com/celestiaorg/oif-solver?rev=a06231ca236ee9ce156900cd2cc9915fbe847cdc#a06231ca236ee9ce156900cd2cc9915fbe847cdc" +source = "git+https://github.com/celestiaorg/oif-solver?branch=jonas%2Fdenylist#ecda6d828a9fce383e8605395b11701f68565b00" dependencies = [ "alloy-consensus", "alloy-contract", @@ -5604,7 +5606,6 @@ dependencies = [ "serde_json", "thiserror 2.0.18", "tokio", - "toml 0.9.12+spec-1.1.0", "tracing", "zeroize", ] @@ -5747,9 +5748,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 +5891,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 +6358,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 +6910,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 +7070,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", 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/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 { 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/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.lock b/solver-cli/Cargo.lock index 22f777a..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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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=2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f#2364b4d5bd6f2b77299fbcf2d1a921ce46923a6f" +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 316a77b..940fc92 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", @@ -27,7 +28,7 @@ solver-runtime = [ "solver-discovery", "solver-order", "solver-pricing", - "solver-settlement", + "solver-settlement-impls", "solver-storage", "solver-types", ] @@ -88,27 +89,28 @@ alloy-primitives = { version = "1.0.37", features = ["std", "serde"], optional = 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" } 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", 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/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 = { path = "../solver-settlement", features = ["testing"] } -solver-storage = { git = "https://github.com/celestiaorg/oif-solver", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc", features = ["testing"] } +solver-settlement-impls = { path = "../solver-settlement", features = ["testing"] } +solver-storage = { git = "https://github.com/celestiaorg/oif-solver", branch = "jonas/denylist", 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..9e1b6c2 --- /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..46405e9 100644 --- a/solver-cli/src/solver/config_gen.rs +++ b/solver-cli/src/solver/config_gen.rs @@ -101,6 +101,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 routes (all-to-all) let mut routes = Vec::new(); for &from_id in &chain_ids { @@ -125,7 +132,7 @@ min_profitability_pct = 0.0 commission_bps = 20 rate_buffer_bps = 15 monitoring_timeout_seconds = 28800 - +{ofac_line} # ============================================================================ # HTTP API # ============================================================================ @@ -235,6 +242,7 @@ fallbacks = ["coingecko"] # SETTLEMENT # ============================================================================ [settlement] +primary = "centralized" settlement_poll_interval_seconds = 12 [settlement.implementations.centralized] @@ -257,6 +265,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"), diff --git a/solver-cli/src/solver/engine.rs b/solver-cli/src/solver/engine.rs index e5cc1b5..618ba3c 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(); @@ -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,136 @@ 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); + } + } + _ => {} + } +} diff --git a/solver-settlement/Cargo.toml b/solver-settlement/Cargo.toml index 3931b4c..2ed9913 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/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", rev = "a06231ca236ee9ce156900cd2cc9915fbe847cdc" } +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..838d812 100644 --- a/solver-settlement/src/centralized.rs +++ b/solver-settlement/src/centralized.rs @@ -265,14 +265,33 @@ 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( @@ -531,11 +550,12 @@ 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)?;