diff --git a/.github/workflows/pull-request.yaml b/.github/workflows/pull-request.yaml index 4c9f763f27..c9fb472a55 100644 --- a/.github/workflows/pull-request.yaml +++ b/.github/workflows/pull-request.yaml @@ -178,7 +178,18 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{ github.event.pull_request.head.sha }} + - run: rustup toolchain install stable --profile minimal + - uses: Swatinem/rust-cache@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + components: rustfmt + - run: cargo build -p openapi-generator + - name: Check for unstaged changes in generated YAML files + run: | + git diff --exit-code crates/solvers/openapi.json || { echo "::error ::OpenAPI spec contains changes: crates/solvers/openapi.json"; exit 1; } - run: npm install @apidevtools/swagger-cli - run: node_modules/.bin/swagger-cli validate crates/orderbook/openapi.yml - run: node_modules/.bin/swagger-cli validate crates/driver/openapi.yml - - run: node_modules/.bin/swagger-cli validate crates/solvers/openapi.yml + - run: node_modules/.bin/swagger-cli validate crates/solvers/openapi.json diff --git a/Cargo.lock b/Cargo.lock index 2e823f767e..27c48cbe8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3120,6 +3120,13 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openapi-generator" +version = "0.1.0" +dependencies = [ + "solvers", +] + [[package]] name = "openssl" version = "0.10.64" @@ -3440,6 +3447,30 @@ dependencies = [ "toml_edit 0.19.14", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.81" @@ -4295,6 +4326,7 @@ dependencies = [ "tower", "tower-http", "tracing", + "utoipa", "web3", ] @@ -4309,7 +4341,9 @@ dependencies = [ "hex", "number", "serde", + "serde_json", "serde_with", + "utoipa", "web3", ] @@ -5139,6 +5173,31 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "utoipa" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "272ebdfbc99111033031d2f10e018836056e4d2c8e2acda76450ec7974269fa7" +dependencies = [ + "indexmap 2.2.5", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3c9f4d08338c1bfa70dde39412a040a884c6f318b3d09aaaf3437a1e52027fc" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.60", +] + [[package]] name = "uuid" version = "1.4.1" diff --git a/crates/openapi-generator/Cargo.toml b/crates/openapi-generator/Cargo.toml new file mode 100644 index 0000000000..dbe947a64d --- /dev/null +++ b/crates/openapi-generator/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "openapi-generator" +version = "0.1.0" +authors = ["Cow Protocol Developers "] +edition = "2021" +license = "MIT OR Apache-2.0" + +[dependencies] + +[build-dependencies] +solvers = { path = "../solvers" } diff --git a/crates/openapi-generator/build.rs b/crates/openapi-generator/build.rs new file mode 100644 index 0000000000..35b612ffbd --- /dev/null +++ b/crates/openapi-generator/build.rs @@ -0,0 +1,13 @@ +// Trick to generate the OpenAPI spec on build time. +// See: https://github.com/juhaku/utoipa/issues/214#issuecomment-1179589373 + +use std::fs; + +const SOLVERS_OPENAPI_PATH: &str = "../solvers/openapi.json"; + +fn main() { + let openapi_yaml = solvers::generate_openapi_json() + .expect("Error generating the solvers OpenAPI documentation"); + fs::write(SOLVERS_OPENAPI_PATH, openapi_yaml) + .expect("Error writing the solvers OpenAPI documentation"); +} diff --git a/crates/openapi-generator/src/lib.rs b/crates/openapi-generator/src/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/crates/openapi-generator/src/lib.rs @@ -0,0 +1 @@ + diff --git a/crates/solvers-dto/Cargo.toml b/crates/solvers-dto/Cargo.toml index e996db1480..2a664ee69f 100644 --- a/crates/solvers-dto/Cargo.toml +++ b/crates/solvers-dto/Cargo.toml @@ -13,5 +13,7 @@ chrono = { workspace = true } hex = { workspace = true } number = { path = "../number" } serde = { workspace = true } +serde_json = { workspace = true } serde_with = { workspace = true } +utoipa = { version = "4.2.0" } web3 = { workspace = true } diff --git a/crates/solvers-dto/src/auction.rs b/crates/solvers-dto/src/auction.rs index 9a0b0fd23e..8260ddfaf8 100644 --- a/crates/solvers-dto/src/auction.rs +++ b/crates/solvers-dto/src/auction.rs @@ -4,61 +4,104 @@ use { bigdecimal::BigDecimal, number::serialization::HexOrDecimalU256, serde::Deserialize, + serde_json::{Number, Value}, serde_with::{serde_as, DisplayFromStr}, std::collections::HashMap, + utoipa::{ + openapi::{ + AllOfBuilder, + ArrayBuilder, + ObjectBuilder, + OneOfBuilder, + Ref, + RefOr, + Schema, + SchemaType::{self}, + }, + ToSchema, + }, web3::types::{H160, H256, U256}, }; +/// The abstract auction to be solved by the searcher. #[serde_as] -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Auction { + /// An opaque identifier for the auction. Will be set to `null` for requests + /// that are not part of an auction (when quoting token prices for example). #[serde_as(as = "Option")] + #[schema(value_type = String)] pub id: Option, - pub tokens: HashMap, + /// A map of token addresses to token information. + pub tokens: HashMap, + /// The solvable orders included in the auction. pub orders: Vec, + /// On-chain liquidity that can be used by the solution. pub liquidity: Vec, + /// The current estimated gas price that will be paid when executing a + /// settlement. Additionally, this is the gas price that is multiplied with + /// a settlement's gas estimate for solution scoring. #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub effective_gas_price: U256, + /// The deadline by which a solution to the auction is required. Requests + /// that go beyond this deadline are expected to be cancelled by the caller. + #[schema(value_type = DateTime)] pub deadline: chrono::DateTime, } +/// CoW Protocol order information relevant to execution. #[serde_as] -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Order { + #[schema(value_type = OrderUid)] #[serde_as(as = "serialize::Hex")] pub uid: [u8; 56], + #[schema(value_type = Token)] pub sell_token: H160, + #[schema(value_type = Token)] pub buy_token: H160, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub sell_amount: U256, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub full_sell_amount: U256, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub buy_amount: U256, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub full_buy_amount: U256, pub fee_policies: Option>, pub valid_to: u32, - pub kind: Kind, + pub kind: OrderKind, + #[schema(value_type = Address)] pub receiver: Option, + #[schema(value_type = Address)] pub owner: H160, + /// Whether or not this order can be partially filled. If this is false, + /// then the order is a "fill-or-kill" order, meaning it needs to be + /// completely filled or not at all. pub partially_fillable: bool, pub pre_interactions: Vec, pub post_interactions: Vec, pub sell_token_source: SellTokenSource, pub buy_token_destination: BuyTokenDestination, - pub class: Class, + pub class: OrderClass, + #[schema(value_type = AppData)] pub app_data: AppDataHash, - pub signing_scheme: SigningScheme, + pub signing_scheme: LegacySigningScheme, #[serde(with = "bytes_hex")] + #[schema(value_type = Signature)] pub signature: Vec, } /// Destination for which the buyAmount should be transferred to order's /// receiver to upon fulfillment -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum BuyTokenDestination { /// Pay trade proceeds as an ERC20 token transfer @@ -68,7 +111,7 @@ pub enum BuyTokenDestination { } /// Source from which the sellAmount should be drawn upon order fulfillment -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum SellTokenSource { /// Direct ERC20 allowances to the Vault relayer contract @@ -80,35 +123,43 @@ pub enum SellTokenSource { } #[serde_as] -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct InteractionData { + #[schema(value_type = Address)] pub target: H160, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub value: U256, + #[schema(value_type = String, example = "0x01020304")] #[serde(with = "bytes_hex")] pub call_data: Vec, } -#[derive(Debug, Deserialize)] +// todo: There is a conflict between solution's SigningScheme which is in +// camelCase. There is no way to keep 2 object with the same name in the OpenAPI +// schema. Temporarily renamed the struct. Must be migrated to the camelCase. +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "lowercase")] -pub enum SigningScheme { +pub enum LegacySigningScheme { Eip712, EthSign, Eip1271, PreSign, } -#[derive(Debug, Deserialize)] +/// How the CoW Protocol order was classified. +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] -pub enum Kind { +pub enum OrderKind { Sell, Buy, } -#[derive(Debug, Deserialize)] +/// How the CoW Protocol order was classified. +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] -pub enum Class { +pub enum OrderClass { Market, Limit, Liquidity, @@ -118,7 +169,11 @@ pub enum Class { #[serde(rename_all = "camelCase")] pub enum FeePolicy { #[serde(rename_all = "camelCase")] - Surplus { factor: f64, max_volume_factor: f64 }, + Surplus { + factor: f64, + /// Never charge more than that percentage of the order volume. + max_volume_factor: f64, + }, #[serde(rename_all = "camelCase")] PriceImprovement { factor: f64, @@ -129,22 +184,42 @@ pub enum FeePolicy { Volume { factor: f64 }, } +impl ToSchema<'static> for FeePolicy { + fn schema() -> (&'static str, RefOr) { + ( + "FeePolicy", + Schema::OneOf( + OneOfBuilder::new() + .description(Some("A fee policy that applies to an order")) + .item(Ref::from_schema_name("SurplusFee")) + .item(Ref::from_schema_name("PriceImprovement")) + .item(Ref::from_schema_name("VolumeFee")) + .build(), + ) + .into(), + ) + } +} + #[serde_as] -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Quote { #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub sell_amount: U256, #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub buy_amount: U256, #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub fee: U256, } #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Token { +pub struct TokenInfo { pub decimals: Option, pub symbol: Option, #[serde_as(as = "Option")] @@ -154,6 +229,54 @@ pub struct Token { pub trusted: bool, } +impl ToSchema<'static> for TokenInfo { + fn schema() -> (&'static str, RefOr) { + let decimals = ObjectBuilder::new() + .schema_type(SchemaType::Integer) + .description(Some( + "The ERC20.decimals value for this token. This may be missing for ERC20 tokens \ + that don't implement the optional metadata extension.", + )); + let symbol = ObjectBuilder::new() + .schema_type(SchemaType::String) + .description(Some( + "The ERC20.symbol value for this token. This may be missing for ERC20 tokens that \ + don't implement the optional metadata extension.", + )); + let reference_price = AllOfBuilder::new() + .description(Some( + "The reference price of this token for the auction used for scoring. This price \ + is only included for tokens for which there are CoW Protocol orders.", + )) + .item(Ref::from_schema_name("NativePrice")); + let available_balance = AllOfBuilder::new() + .description(Some( + "The balance held by the Settlement contract that is available during a \ + settlement.", + )) + .item(Ref::from_schema_name("TokenAmount")); + let trusted = ObjectBuilder::new() + .schema_type(SchemaType::Boolean) + .description(Some( + "A flag which indicates that solvers are allowed to perform gas cost \ + optimizations for this token by not routing the trades via an AMM, and instead \ + use its available balances, as specified by CIP-2.", + )); + let auction = ObjectBuilder::new() + .description(Some("Information about an ERC20 token.")) + .required("availableBalance") + .required("trusted") + .property("decimals", decimals) + .property("symbol", symbol) + .property("referencePrice", reference_price) + .property("availableBalance", available_balance) + .property("trusted", trusted) + .build(); + + ("TokenInfo", Schema::Object(auction).into()) + } +} + #[allow(clippy::enum_variant_names)] #[derive(Debug, Deserialize)] #[serde(tag = "kind", rename_all = "camelCase")] @@ -165,6 +288,78 @@ pub enum Liquidity { LimitOrder(ForeignLimitOrder), } +// todo: Currently, it strictly follows the manual api schema. This has to be +// automated and deleted. +impl ToSchema<'static> for Liquidity { + fn schema() -> (&'static str, RefOr) { + let id = ObjectBuilder::new() + .schema_type(SchemaType::String) + .description(Some( + "An opaque ID used for uniquely identifying the liquidity within a single auction \ + (note that they are **not** guaranteed to be unique across auctions). This ID is \ + used in the solution for matching interactions with the executed liquidity.", + )); + let address = AllOfBuilder::new() + .description(Some( + "A rough approximation of gas units required to use this liquidity on-chain.", + )) + .item(Ref::from_schema_name("Address")); + let gas_estimate = AllOfBuilder::new() + .description(Some( + "A rough approximation of gas units required to use this liquidity on-chain.", + )) + .item(Ref::from_schema_name("BigInt")); + let liquidity_obj = ObjectBuilder::new() + .property("id", id) + .property("address", address) + .property("gasEstimate", gas_estimate) + .required("id") + .required("address") + .required("gasEstimate") + .build(); + let liquidity = AllOfBuilder::new() + .description(Some( + "On-chain liquidity that can be used in a solution. This liquidity is provided to \ + facilitate onboarding new solvers. Additional liquidity that is not included in \ + this set may still be used in solutions.", + )) + .item(Ref::from_schema_name("LiquidityParameters")) + .item(Schema::Object(liquidity_obj)) + .build(); + + ("Liquidity", Schema::AllOf(liquidity).into()) + } +} + +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Deserialize)] +#[serde(tag = "kind", rename_all = "camelCase", deny_unknown_fields)] +pub enum LiquidityParameters { + ConstantProduct(ConstantProductPool), + WeightedProduct(WeightedProductPool), + Stable(StablePool), + ConcentratedLiquidity(ConcentratedLiquidityPool), + LimitOrder(ForeignLimitOrder), +} + +impl ToSchema<'static> for LiquidityParameters { + fn schema() -> (&'static str, RefOr) { + ( + "LiquidityParameters", + Schema::OneOf( + OneOfBuilder::new() + .item(Ref::from_schema_name("ConstantProductPool")) + .item(Ref::from_schema_name("WeightedProductPool")) + .item(Ref::from_schema_name("StablePool")) + .item(Ref::from_schema_name("ConcentratedLiquidityPool")) + .item(Ref::from_schema_name("ForeignLimitOrder")) + .build(), + ) + .into(), + ) + } +} + #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -178,9 +373,40 @@ pub struct ConstantProductPool { pub fee: BigDecimal, } +impl ToSchema<'static> for ConstantProductPool { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["constantProduct"])); + let tokens = ObjectBuilder::new() + .description(Some("A mapping of token address to its reserve amounts.")) + .additional_properties(Some(Ref::from_schema_name("TokenReserve"))); + let constant_product_pool = ObjectBuilder::new() + .description(Some( + "A UniswapV2-like constant product liquidity pool for a token pair.", + )) + .required("kind") + .required("router") + .required("tokens") + .required("fee") + .property("kind", kind) + .property("router", Ref::from_schema_name("Address")) + .property("tokens", tokens) + .property("fee", Ref::from_schema_name("Decimal")) + .build(); + + ( + "ConstantProductPool", + Schema::Object(constant_product_pool).into(), + ) + } +} + +/// A reserve of tokens in an on-chain liquidity pool. #[serde_as] -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] +#[schema(title = "TokenReserve")] pub struct ConstantProductReserve { #[serde_as(as = "HexOrDecimalU256")] pub balance: U256, @@ -200,6 +426,52 @@ pub struct WeightedProductPool { pub version: WeightedProductVersion, } +impl ToSchema<'static> for WeightedProductPool { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["weightedProduct"])); + let tokens = ObjectBuilder::new() + .description(Some( + "A mapping of token address to its reserve amounts with weights.", + )) + .additional_properties(Some(Schema::AllOf( + AllOfBuilder::new() + .item(Ref::from_schema_name("TokenReserve")) + .item( + ObjectBuilder::new() + .required("weight") + .property("weight", Ref::from_schema_name("Decimal")) + .property("scalingFactor", Ref::from_schema_name("Decimal")) + .build(), + ) + .build(), + ))); + let version = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["v0", "v3Plus"])); + let weighted_product_pool = ObjectBuilder::new() + .description(Some( + "A Balancer-like weighted product liquidity pool of N tokens.", + )) + .required("kind") + .required("tokens") + .required("fee") + .required("balancer_pool_id") + .property("kind", kind) + .property("tokens", tokens) + .property("fee", Ref::from_schema_name("Decimal")) + .property("balancer_pool_id", Ref::from_schema_name("BalancerPoolId")) + .property("version", version) + .build(); + + ( + "WeightedProductPool", + Schema::Object(weighted_product_pool).into(), + ) + } +} + #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -231,6 +503,46 @@ pub struct StablePool { pub fee: BigDecimal, } +impl ToSchema<'static> for StablePool { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["stable"])); + let tokens = ObjectBuilder::new() + .description(Some( + "A mapping of token address to token balance and scaling rate.", + )) + .additional_properties(Some(Schema::AllOf( + AllOfBuilder::new() + .item(Ref::from_schema_name("TokenReserve")) + .item( + ObjectBuilder::new() + .required("scalingFactor") + .property("scalingFactor", Ref::from_schema_name("Decimal")) + .build(), + ) + .build(), + ))); + let stable_pool = Schema::Object( + ObjectBuilder::new() + .description(Some("A Curve-like stable pool of N tokens.")) + .required("kind") + .required("tokens") + .required("amplificationParameter") + .required("fee") + .required("balancer_pool_id") + .property("kind", kind) + .property("tokens", tokens) + .property("amplificationParameter", Ref::from_schema_name("Decimal")) + .property("fee", Ref::from_schema_name("Decimal")) + .property("balancer_pool_id", Ref::from_schema_name("BalancerPoolId")) + .build(), + ); + + ("StablePool", stable_pool.into()) + } +} + #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -260,6 +572,42 @@ pub struct ConcentratedLiquidityPool { pub fee: BigDecimal, } +impl ToSchema<'static> for ConcentratedLiquidityPool { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["concentratedLiquidity"])); + let liquidity_net = ObjectBuilder::new() + .description(Some("A map of tick indices to their liquidity values.")) + .additional_properties(Some(Ref::from_schema_name("I128"))); + let tokens = ArrayBuilder::new().items(Ref::from_schema_name("Token")); + let concentrated_liquidity_pool = ObjectBuilder::new() + .description(Some("A Uniswap V3-like concentrated liquidity pool.")) + .required("kind") + .required("router") + .required("tokens") + .required("sqrtPrice") + .required("liquidity") + .required("tick") + .required("liquidityNet") + .required("fee") + .property("kind", kind) + .property("router", Ref::from_schema_name("Address")) + .property("tokens", tokens) + .property("sqrtPrice", Ref::from_schema_name("U256")) + .property("liquidity", Ref::from_schema_name("U128")) + .property("tick", Ref::from_schema_name("I32")) + .property("liquidityNet", liquidity_net) + .property("fee", Ref::from_schema_name("Decimal")) + .build(); + + ( + "ConcentratedLiquidityPool", + Schema::Object(concentrated_liquidity_pool).into(), + ) + } +} + #[serde_as] #[derive(Debug, Deserialize)] #[serde(rename_all = "camelCase")] @@ -269,6 +617,7 @@ pub struct ForeignLimitOrder { #[serde_as(as = "HexOrDecimalU256")] pub gas_estimate: U256, #[serde_as(as = "serialize::Hex")] + // todo: not used/not a part of the API schema for some reason pub hash: [u8; 32], pub maker_token: H160, pub taker_token: H160, @@ -279,3 +628,220 @@ pub struct ForeignLimitOrder { #[serde_as(as = "HexOrDecimalU256")] pub taker_token_fee_amount: U256, } + +impl ToSchema<'static> for ForeignLimitOrder { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["limitOrder"])); + let foreign_limit_order = ObjectBuilder::new() + .description(Some("A 0x-like limit order external to CoW Protocol.")) + .required("kind") + .required("makerToken") + .required("takerToken") + .required("makerAmount") + .required("takerAmount") + .required("takerTokenFeeAmount") + .property("kind", kind) + .property("makerToken", Ref::from_schema_name("Token")) + .property("takerToken", Ref::from_schema_name("Token")) + .property("makerAmount", Ref::from_schema_name("TokenAmount")) + .property("takerAmount", Ref::from_schema_name("TokenAmount")) + .property("takerTokenFeeAmount", Ref::from_schema_name("TokenAmount")) + .build(); + + ( + "ForeignLimitOrder", + Schema::Object(foreign_limit_order).into(), + ) + } +} + +// Structs for the utoipa OpenAPI schema generator. + +/// The price in wei of the native token (Ether on Mainnet for example) to buy +/// 10**18 of a token. +#[derive(ToSchema)] +#[schema(example = "1234567890")] +#[allow(dead_code)] +pub struct NativePrice(String); + +/// An ISO-8601 formatted date-time. +#[derive(ToSchema)] +#[schema(example = "1970-01-01T00:00:00.000Z")] +#[allow(dead_code)] +pub struct DateTime(String); + +/// An arbitrary-precision integer value. +#[derive(ToSchema)] +#[schema(example = "1234567890")] +#[allow(dead_code)] +pub struct BigInt(String); + +/// An arbitrary-precision decimal value. +#[derive(ToSchema)] +#[schema(example = "13.37")] +#[allow(dead_code)] +pub struct Decimal(String); + +/// A hex-encoded 32 byte string containing the pool address (0..20), the pool +/// specialization (20..22) and the poolnonce (22..32). +#[derive(ToSchema)] +#[schema(example = "0xc88c76dd8b92408fe9bea1a54922a31e232d873c0002000000000000000005b2")] +#[allow(dead_code)] +pub struct BalancerPoolId(String); + +#[serde_as] +#[derive(ToSchema, Deserialize)] +#[serde(rename_all = "camelCase", deny_unknown_fields)] +pub struct TokenReserve { + #[schema(value_type = TokenAmount)] + pub balance: U256, +} + +/// 256 bit unsigned integer in decimal notation. +#[derive(ToSchema)] +#[schema(as = U256, example = "1234567890")] +#[allow(dead_code)] +pub struct U256Schema(String); + +/// 128 bit unsigned integer in decimal notation. +#[derive(ToSchema)] +#[schema(example = "1234567890")] +#[allow(dead_code)] +pub struct U128(String); + +/// 128 bit signed integer in decimal notation. +#[derive(ToSchema)] +#[schema(example = "-1234567890")] +#[allow(dead_code)] +pub struct I128(String); + +/// 32 bit signed integer in decimal notation. +#[derive(ToSchema)] +#[schema(example = "-12345")] +#[allow(dead_code)] +pub struct I32(String); + +/// Unique identifier for the order. Order UIDs are 56 bytes long, where bytes +/// [0, 32) represent the order digest used for signing, bytes [32, 52) +/// represent the owner address and bytes [52, 56) represent the order's +/// `validTo` field. +#[derive(ToSchema)] +#[schema( + example = "0x30cff40d9f60caa68a37f0ee73253ad6ad72b45580c945fe3ab67596476937197854163b1b0d24e77dca702b97b5cc33e0f83dcb626122a6" +)] +#[allow(dead_code)] +pub struct OrderUid(String); + +/// Signature bytes. +#[derive(ToSchema)] +#[schema( + example = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +)] +#[allow(dead_code)] +pub struct Signature(String); + +pub struct SurplusFee { + pub factor: f64, + pub max_volume_factor: f64, +} + +impl ToSchema<'static> for SurplusFee { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["surplus"])); + let factor = ObjectBuilder::new() + .description(Some( + "The factor of the user surplus that the protocol will request from the solver \ + after settling the order", + )) + .schema_type(SchemaType::Number) + .example(Number::from_f64(0.5).map(Value::Number)); + let max_volume_factor = ObjectBuilder::new() + .description(Some( + "Never charge more than that percentage of the order volume.", + )) + .schema_type(SchemaType::Number) + .example(Number::from_f64(0.05).map(Value::Number)) + .minimum(Some(0.0)) + .maximum(Some(0.99999)); + let surplus_fee = ObjectBuilder::new() + .description(Some( + "If the order receives more than limit price, pay the protocol a factor of the \ + difference.", + )) + .property("kind", kind) + .property("factor", factor) + .property("maxVolumeFactor", max_volume_factor) + .build(); + + ("SurplusFee", Schema::Object(surplus_fee).into()) + } +} + +pub struct PriceImprovement { + pub factor: f64, + pub max_volume_factor: f64, + pub quote: Quote, +} + +impl ToSchema<'static> for PriceImprovement { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["priceImprovement"])); + let factor = ObjectBuilder::new() + .description(Some( + "The factor of the user surplus that the protocol will request from the solver \ + after settling the order", + )) + .schema_type(SchemaType::Number) + .example(Number::from_f64(0.5).map(Value::Number)); + let max_volume_factor = ObjectBuilder::new() + .description(Some( + "Never charge more than that percentage of the order volume.", + )) + .schema_type(SchemaType::Number) + .example(Number::from_f64(0.01).map(Value::Number)) + .minimum(Some(0.0)) + .maximum(Some(0.99999)); + let price_improvement = ObjectBuilder::new() + .description(Some( + "A cut from the price improvement over the best quote is taken as a protocol fee.", + )) + .property("kind", kind) + .property("factor", factor) + .property("maxVolumeFactor", max_volume_factor) + .property("quote", Ref::from_schema_name("Quote")) + .build(); + + ("PriceImprovement", Schema::Object(price_improvement).into()) + } +} + +pub struct VolumeFee { + pub factor: f64, +} + +impl ToSchema<'static> for VolumeFee { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["volume"])); + let factor = ObjectBuilder::new() + .description(Some( + "The fraction of the order's volume that the protocol will request from the \ + solver after settling the order.", + )) + .schema_type(SchemaType::Number) + .example(Number::from_f64(0.5).map(Value::Number)); + let volume_fee = ObjectBuilder::new() + .property("kind", kind) + .property("factor", factor) + .build(); + + ("VolumeFee", Schema::Object(volume_fee).into()) + } +} diff --git a/crates/solvers-dto/src/common.rs b/crates/solvers-dto/src/common.rs new file mode 100644 index 0000000000..0f16d9380e --- /dev/null +++ b/crates/solvers-dto/src/common.rs @@ -0,0 +1,37 @@ +use utoipa::ToSchema; + +// Structs for the utoipa OpenAPI schema generator. + +/// Signature bytes. +#[derive(ToSchema)] +#[schema( + example = "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +)] +#[allow(dead_code)] +pub struct Signature(String); + +/// 32 bytes of arbitrary application specific data that can be added to an +/// order. This can also be used to ensure uniqueness between two orders with +/// otherwise the exact same parameters. +#[derive(ToSchema)] +#[schema(example = "0x0000000000000000000000000000000000000000000000000000000000000000")] +#[allow(dead_code)] +pub struct AppData(String); + +/// Amount of an ERC20 token. 256 bit unsigned integer in decimal notation. +#[derive(ToSchema)] +#[schema(example = "1234567890")] +#[allow(dead_code)] +pub struct TokenAmount(String); + +/// An Ethereum public address. +#[derive(ToSchema)] +#[schema(example = "0x0000000000000000000000000000000000000000")] +#[allow(dead_code)] +pub struct Address(String); + +/// An ERC20 token address. +#[derive(ToSchema)] +#[schema(example = "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB")] +#[allow(dead_code)] +pub struct Token(String); diff --git a/crates/solvers-dto/src/lib.rs b/crates/solvers-dto/src/lib.rs index b5cbc5a0da..d29ad90735 100644 --- a/crates/solvers-dto/src/lib.rs +++ b/crates/solvers-dto/src/lib.rs @@ -2,6 +2,7 @@ //! communicate with the driver. pub mod auction; +pub mod common; pub mod notification; pub mod solution; diff --git a/crates/solvers-dto/src/notification.rs b/crates/solvers-dto/src/notification.rs index ca78eb6702..38d001fd90 100644 --- a/crates/solvers-dto/src/notification.rs +++ b/crates/solvers-dto/src/notification.rs @@ -4,6 +4,10 @@ use { serde::Deserialize, serde_with::{serde_as, DisplayFromStr}, std::collections::BTreeSet, + utoipa::{ + openapi::{ObjectBuilder, RefOr, Schema, SchemaType}, + ToSchema, + }, web3::types::{AccessList, H160, H256, U256}, }; @@ -18,6 +22,54 @@ pub struct Notification { pub kind: Kind, } +// serde(flatten) has a conflict with the current API schema +impl ToSchema<'static> for Notification { + fn schema() -> (&'static str, RefOr) { + let auction_id = ObjectBuilder::new() + .description(Some( + "The auction ID of the auction that the solution was providedfor.", + )) + .schema_type(SchemaType::String); + let solution_id = ObjectBuilder::new() + .description(Some( + "The solution ID within the auction for which the notification applies", + )) + .schema_type(SchemaType::Number); + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some([ + "timeout", + "emptySolution", + "duplicatedSolutionId", + "simulationFailed", + "invalidClearingPrices", + "missingPrice", + "invalidExecutedAmount", + "nonBufferableTokensUsed", + "solverAccountInsufficientBalance", + "success", + "revert", + "driverError", + "cancelled", + "fail", + "postprocessingTimedOut", + ])); + let notification = ObjectBuilder::new() + .description(Some( + "A notification that informs the solver how its solution performed in the \ + auction. Depending on the notification type additional meta data may be attached \ + but this is not guaranteed to be stable.", + )) + .schema_type(SchemaType::Object) + .property("auctionId", auction_id) + .property("solutionId", solution_id) + .property("kind", kind) + .build(); + + ("Notification", Schema::Object(notification).into()) + } +} + #[serde_as] #[derive(Debug, Deserialize)] #[serde(untagged)] diff --git a/crates/solvers-dto/src/solution.rs b/crates/solvers-dto/src/solution.rs index ffd2c2ede8..44e27952bd 100644 --- a/crates/solvers-dto/src/solution.rs +++ b/crates/solvers-dto/src/solution.rs @@ -2,26 +2,50 @@ use { super::serialize, number::serialization::HexOrDecimalU256, serde::Serialize, + serde_json::Value, serde_with::serde_as, std::collections::HashMap, + utoipa::{ + openapi::{ + AllOfBuilder, + ArrayBuilder, + ObjectBuilder, + OneOfBuilder, + Ref, + RefOr, + Schema, + SchemaType, + }, + ToSchema, + }, web3::types::{H160, U256}, }; -#[derive(Debug, Serialize, Default)] +/// Proposed solutions to settle some of the orders in the auction. +#[derive(Debug, Serialize, Default, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Solutions { pub solutions: Vec, } +/// A computed solution for a given auction. #[serde_as] -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Solution { + /// An opaque identifier for the solution. This is a solver generated number + /// that is unique across multiple solutions within the auction. + #[schema(value_type = f64, format = Int64)] pub id: u64, + /// A clearing price map of token address to price. The price can have + /// arbitrary denomination. #[serde_as(as = "HashMap<_, HexOrDecimalU256>")] pub prices: HashMap, + /// CoW Protocol order trades included in the solution. pub trades: Vec, + /// Interactions to encode within a settlement. pub interactions: Vec, + /// How many units of gas this solution is estimated to cost. #[serde(skip_serializing_if = "Option::is_none")] pub gas: Option, } @@ -33,6 +57,20 @@ pub enum Trade { Jit(JitTrade), } +impl ToSchema<'static> for Trade { + fn schema() -> (&'static str, RefOr) { + let trade = OneOfBuilder::new() + .description(Some( + "A trade for a CoW Protocol order included in a solution.", + )) + .item(Ref::from_schema_name("Fulfillment")) + .item(Ref::from_schema_name("JitTrade")) + .build(); + + ("Trade", Schema::OneOf(trade).into()) + } +} + #[serde_as] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -46,6 +84,41 @@ pub struct Fulfillment { pub fee: Option, } +impl ToSchema<'static> for Fulfillment { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["fulfillment"])); + let order = AllOfBuilder::new() + .item(Ref::from_schema_name("OrderUid")) + .description(Some( + "A reference by UID of the order to execute in a solution. The order must be \ + included in the auction input.", + )); + let executed_amount = AllOfBuilder::new() + .description(Some( + "The amount of the order that was executed. This is denoted in 'sellToken' for \ + sell orders, and 'buyToken' for buy orders.", + )) + .item(Ref::from_schema_name("TokenAmount")); + let fee = ObjectBuilder::new().description(Some( + "The sell token amount that should be taken as a fee for this trade. This only gets \ + returned for limit orders and only refers to the actual amount filled by the trade.", + )); + let fulfillment = ObjectBuilder::new() + .description(Some("A trade which fulfills an order from the auction.")) + .required("kind") + .required("order") + .property("kind", kind) + .property("order", order) + .property("executedAmount", executed_amount) + .property("fee", fee) + .build(); + + ("Fulfillment", Schema::Object(fulfillment).into()) + } +} + #[serde_as] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -55,34 +128,73 @@ pub struct JitTrade { pub executed_amount: U256, } +impl ToSchema<'static> for JitTrade { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["jit"])); + let order = AllOfBuilder::new() + .description(Some( + "The just-in-time liquidity order to execute in a solution.", + )) + .item(Ref::from_schema_name("JitOrder")); + let executed_amount = AllOfBuilder::new() + .description(Some( + "The amount of the order that was executed. This is denoted in 'sellToken' for \ + sell orders, and 'buyToken' for buy orders.", + )) + .item(Ref::from_schema_name("TokenAmount")); + let jit_trade = ObjectBuilder::new() + .description(Some("A trade with a JIT order.")) + .required("kind") + .required("order") + .required("executedAmount") + .property("kind", kind) + .property("order", order) + .property("executedAmount", executed_amount) + .build(); + + ("JitTrade", Schema::Object(jit_trade).into()) + } +} + +/// A just-in-time liquidity order included in a settlement. #[serde_as] -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct JitOrder { + #[schema(value_type = Token)] pub sell_token: H160, + #[schema(value_type = Token)] pub buy_token: H160, + #[schema(value_type = Address)] pub receiver: H160, #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub sell_amount: U256, #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub buy_amount: U256, pub valid_to: u32, #[serde_as(as = "serialize::Hex")] + #[schema(value_type = AppData)] pub app_data: [u8; 32], #[serde_as(as = "HexOrDecimalU256")] + #[schema(value_type = TokenAmount)] pub fee_amount: U256, - pub kind: Kind, + pub kind: OrderKind, pub partially_fillable: bool, pub sell_token_balance: SellTokenBalance, pub buy_token_balance: BuyTokenBalance, pub signing_scheme: SigningScheme, #[serde_as(as = "serialize::Hex")] + #[schema(value_type = Signature)] pub signature: Vec, } #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub enum Kind { +pub enum OrderKind { Sell, Buy, } @@ -94,11 +206,45 @@ pub enum Interaction { Custom(CustomInteraction), } +// todo: Currently, it strictly follows the manual api schema. This has to be +// automated and deleted. +impl ToSchema<'static> for Interaction { + fn schema() -> (&'static str, RefOr) { + let internalize = ObjectBuilder::new().property( + "internalize", + ObjectBuilder::new() + .schema_type(SchemaType::Boolean) + .description(Some( + "A flag indicating that the interaction should be 'internalized', as \ + specified by CIP-2.", + )), + ); + let interaction = AllOfBuilder::new() + .description(Some("An interaction to execute as part of a settlement.")) + .item(internalize) + .item( + OneOfBuilder::new() + .item(Ref::from_schema_name("LiquidityInteraction")) + .item(Ref::from_schema_name("CustomInteraction")), + ) + .build(); + + ("Interaction", Schema::AllOf(interaction).into()) + } +} + +#[derive(Debug, Serialize)] +pub enum InteractionType { + Liquidity(LiquidityInteraction), + Custom(CustomInteraction), +} + #[serde_as] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct LiquidityInteraction { pub internalize: bool, + /// The ID of executed liquidity provided in the auction input. pub id: String, pub input_token: H160, pub output_token: H160, @@ -108,6 +254,42 @@ pub struct LiquidityInteraction { pub output_amount: U256, } +impl ToSchema<'static> for LiquidityInteraction { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["liquidity"])); + let id = ObjectBuilder::new() + .schema_type(SchemaType::String) + .description(Some( + "The ID of executed liquidity provided in the auction input.", + )); + let liquidity_interaction = ObjectBuilder::new() + .description(Some( + "Interaction representing the execution of liquidity that was passed in with the \ + auction.", + )) + .required("kind") + .required("id") + .required("inputToken") + .required("outputToken") + .required("inputAmount") + .required("outputAmount") + .property("kind", kind) + .property("id", id) + .property("inputToken", Ref::from_schema_name("Token")) + .property("outputToken", Ref::from_schema_name("Token")) + .property("inputAmount", Ref::from_schema_name("TokenAmount")) + .property("outputAmount", Ref::from_schema_name("TokenAmount")) + .build(); + + ( + "LiquidityInteraction", + Schema::Object(liquidity_interaction).into(), + ) + } +} + #[serde_as] #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] @@ -116,43 +298,82 @@ pub struct CustomInteraction { pub target: H160, #[serde_as(as = "HexOrDecimalU256")] pub value: U256, + /// The EVM calldata bytes. #[serde(rename = "callData")] #[serde_as(as = "serialize::Hex")] pub calldata: Vec, + /// ERC20 allowances that are required for this custom interaction. pub allowances: Vec, pub inputs: Vec, pub outputs: Vec, } -/// An interaction that can be executed as part of an order's pre- or -/// post-interactions. -#[serde_as] -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OrderInteraction { - pub target: H160, - #[serde_as(as = "HexOrDecimalU256")] - pub value: U256, - #[serde(rename = "callData")] - #[serde_as(as = "serialize::Hex")] - pub calldata: Vec, +impl ToSchema<'static> for CustomInteraction { + fn schema() -> (&'static str, RefOr) { + let kind = ObjectBuilder::new() + .schema_type(SchemaType::String) + .enum_values(Some(["custom"])); + let call_data = ObjectBuilder::new() + .schema_type(SchemaType::String) + .description(Some("The EVM calldata bytes.")) + .example(Some(Value::String("0x01020304".to_string()))); + let allowances = ArrayBuilder::new() + .items(Ref::from_schema_name("Allowance")) + .description(Some( + "ERC20 allowances that are required for this custom interaction.", + )); + let asset = ArrayBuilder::new() + .items(Ref::from_schema_name("Asset")) + .build(); + let custom_interaction = ObjectBuilder::new() + .description(Some( + "A searcher-specified custom interaction to be included in the final settlement.", + )) + .required("kind") + .required("target") + .required("value") + .required("callData") + .required("inputs") + .required("outputs") + .property("kind", kind) + .property("target", Ref::from_schema_name("Address")) + .property("value", Ref::from_schema_name("TokenAmount")) + .property("callData", call_data) + .property("allowances", allowances) + .property("inputs", asset.clone()) + .property("outputs", asset) + .build(); + + ( + "CustomInteraction", + Schema::Object(custom_interaction).into(), + ) + } } +/// A token address with an amount. #[serde_as] -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Asset { + #[schema(value_type = Token)] pub token: H160, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub amount: U256, } +/// An ERC20 allowance from the settlement contract to some spender that is +/// required for a custom interaction. #[serde_as] -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct Allowance { + #[schema(value_type = Token)] pub token: H160, + #[schema(value_type = Address)] pub spender: H160, + #[schema(value_type = TokenAmount)] #[serde_as(as = "HexOrDecimalU256")] pub amount: U256, } @@ -166,6 +387,21 @@ pub enum SellTokenBalance { External, } +impl ToSchema<'static> for SellTokenBalance { + fn schema() -> (&'static str, RefOr) { + let sell_token_balance = ObjectBuilder::new() + .description(Some("Where should the sell token be drawn from?")) + .schema_type(SchemaType::String) + .enum_values(Some(["erc20", "internal", "external"])) + .build(); + + ( + "SellTokenBalance", + Schema::Object(sell_token_balance).into(), + ) + } +} + #[derive(Debug, Default, Serialize)] #[serde(rename_all = "camelCase")] pub enum BuyTokenBalance { @@ -174,6 +410,18 @@ pub enum BuyTokenBalance { Internal, } +impl ToSchema<'static> for BuyTokenBalance { + fn schema() -> (&'static str, RefOr) { + let buy_token_balance = ObjectBuilder::new() + .description(Some("Where should the buy token be transferred to?")) + .schema_type(SchemaType::String) + .enum_values(Some(["erc20", "internal"])) + .build(); + + ("BuyTokenBalance", Schema::Object(buy_token_balance).into()) + } +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub enum SigningScheme { @@ -182,3 +430,15 @@ pub enum SigningScheme { PreSign, Eip1271, } + +impl ToSchema<'static> for SigningScheme { + fn schema() -> (&'static str, RefOr) { + let signing_scheme = ObjectBuilder::new() + .description(Some("How was the order signed?")) + .schema_type(SchemaType::String) + .enum_values(Some(["eip712", "ethSign", "preSign", "eip1271"])) + .build(); + + ("SigningScheme", Schema::Object(signing_scheme).into()) + } +} diff --git a/crates/solvers/Cargo.toml b/crates/solvers/Cargo.toml index be3693d0d3..d0d5060e18 100644 --- a/crates/solvers/Cargo.toml +++ b/crates/solvers/Cargo.toml @@ -39,6 +39,7 @@ tokio = { workspace = true, features = ["macros", "rt-multi-thread", "signal", " toml = { workspace = true } tower = "0.4" tower-http = { version = "0.4", features = ["limit", "trace"] } +utoipa = { version = "4.2.0", features = ["axum_extras"] } web3 = { workspace = true } # TODO Once solvers are ported and E2E tests set up, slowly migrate code and diff --git a/crates/solvers/openapi.json b/crates/solvers/openapi.json new file mode 100644 index 0000000000..f4a98fbf3c --- /dev/null +++ b/crates/solvers/openapi.json @@ -0,0 +1,1205 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Solver Engine API", + "description": "The API implemented by solver engines interacting with the reference driver implementation.", + "license": { + "name": "MIT OR Apache-2.0" + }, + "version": "0.1.0" + }, + "paths": { + "/notify": { + "post": { + "tags": [ + "routes::notify" + ], + "summary": "Receive a status notification about a previously provided solution.", + "description": "Receive a status notification about a previously provided solution.", + "operationId": "notify", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "A notification that informs the solver how its solution performed in the auction. Depending on the notification type additional meta data may be attached but this is not guaranteed to be stable.", + "properties": { + "auctionId": { + "type": "string", + "description": "The auction ID of the auction that the solution was providedfor." + }, + "kind": { + "type": "string", + "enum": [ + "timeout", + "emptySolution", + "duplicatedSolutionId", + "simulationFailed", + "invalidClearingPrices", + "missingPrice", + "invalidExecutedAmount", + "nonBufferableTokensUsed", + "solverAccountInsufficientBalance", + "success", + "revert", + "driverError", + "cancelled", + "fail", + "postprocessingTimedOut" + ] + }, + "solutionId": { + "type": "number", + "description": "The solution ID within the auction for which the notification applies" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Notification successfully received." + } + } + } + }, + "/solve": { + "post": { + "tags": [ + "routes::solve" + ], + "summary": "Solve the passed in auction instance.", + "description": "Solve the passed in auction instance.", + "operationId": "solve", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Auction" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Auction successfully solved.", + "content": { + "application/json": { + "schema": { + "type": "object", + "description": "Proposed solutions to settle some of the orders in the auction.", + "required": [ + "solutions" + ], + "properties": { + "solutions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Solution" + } + } + } + } + } + } + }, + "400": { + "description": "There is something wrong with the request." + }, + "429": { + "description": "The solver cannot keep up. It is too busy to handle more requests." + }, + "500": { + "description": "Something went wrong when handling the request." + } + } + } + } + }, + "components": { + "schemas": { + "Address": { + "type": "string", + "description": "An Ethereum public address.", + "example": "0x0000000000000000000000000000000000000000" + }, + "Allowance": { + "type": "object", + "description": "An ERC20 allowance from the settlement contract to some spender that is\nrequired for a custom interaction.", + "required": [ + "token", + "spender", + "amount" + ], + "properties": { + "amount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "spender": { + "$ref": "#/components/schemas/Address" + }, + "token": { + "$ref": "#/components/schemas/Token" + } + } + }, + "AppData": { + "type": "string", + "description": "32 bytes of arbitrary application specific data that can be added to an\norder. This can also be used to ensure uniqueness between two orders with\notherwise the exact same parameters.", + "example": "0x0000000000000000000000000000000000000000000000000000000000000000" + }, + "Asset": { + "type": "object", + "description": "A token address with an amount.", + "required": [ + "token", + "amount" + ], + "properties": { + "amount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "token": { + "$ref": "#/components/schemas/Token" + } + } + }, + "Auction": { + "type": "object", + "description": "The abstract auction to be solved by the searcher.", + "required": [ + "tokens", + "orders", + "liquidity", + "effectiveGasPrice", + "deadline" + ], + "properties": { + "deadline": { + "$ref": "#/components/schemas/DateTime" + }, + "effectiveGasPrice": { + "$ref": "#/components/schemas/TokenAmount" + }, + "id": { + "type": "string", + "description": "An opaque identifier for the auction. Will be set to `null` for requests\nthat are not part of an auction (when quoting token prices for example)." + }, + "liquidity": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Liquidity" + }, + "description": "On-chain liquidity that can be used by the solution." + }, + "orders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Order" + }, + "description": "The solvable orders included in the auction." + }, + "tokens": { + "type": "object", + "description": "A map of token addresses to token information.", + "additionalProperties": { + "$ref": "#/components/schemas/TokenInfo" + } + } + } + }, + "BalancerPoolId": { + "type": "string", + "description": "A hex-encoded 32 byte string containing the pool address (0..20), the pool\nspecialization (20..22) and the poolnonce (22..32).", + "example": "0xc88c76dd8b92408fe9bea1a54922a31e232d873c0002000000000000000005b2" + }, + "BigInt": { + "type": "string", + "description": "An arbitrary-precision integer value.", + "example": "1234567890" + }, + "BuyTokenBalance": { + "type": "string", + "description": "Where should the buy token be transferred to?", + "enum": [ + "erc20", + "internal" + ] + }, + "BuyTokenDestination": { + "type": "string", + "description": "Destination for which the buyAmount should be transferred to order's\nreceiver to upon fulfillment", + "enum": [ + "erc20", + "internal" + ] + }, + "ConcentratedLiquidityPool": { + "type": "object", + "description": "A Uniswap V3-like concentrated liquidity pool.", + "required": [ + "kind", + "router", + "tokens", + "sqrtPrice", + "liquidity", + "tick", + "liquidityNet", + "fee" + ], + "properties": { + "fee": { + "$ref": "#/components/schemas/Decimal" + }, + "kind": { + "type": "string", + "enum": [ + "concentratedLiquidity" + ] + }, + "liquidity": { + "$ref": "#/components/schemas/U128" + }, + "liquidityNet": { + "type": "object", + "description": "A map of tick indices to their liquidity values.", + "additionalProperties": { + "$ref": "#/components/schemas/I128" + } + }, + "router": { + "$ref": "#/components/schemas/Address" + }, + "sqrtPrice": { + "$ref": "#/components/schemas/U256" + }, + "tick": { + "$ref": "#/components/schemas/I32" + }, + "tokens": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Token" + } + } + } + }, + "ConstantProductPool": { + "type": "object", + "description": "A UniswapV2-like constant product liquidity pool for a token pair.", + "required": [ + "kind", + "router", + "tokens", + "fee" + ], + "properties": { + "fee": { + "$ref": "#/components/schemas/Decimal" + }, + "kind": { + "type": "string", + "enum": [ + "constantProduct" + ] + }, + "router": { + "$ref": "#/components/schemas/Address" + }, + "tokens": { + "type": "object", + "description": "A mapping of token address to its reserve amounts.", + "additionalProperties": { + "$ref": "#/components/schemas/TokenReserve" + } + } + } + }, + "CustomInteraction": { + "type": "object", + "description": "A searcher-specified custom interaction to be included in the final settlement.", + "required": [ + "kind", + "target", + "value", + "callData", + "inputs", + "outputs" + ], + "properties": { + "allowances": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Allowance" + }, + "description": "ERC20 allowances that are required for this custom interaction." + }, + "callData": { + "type": "string", + "description": "The EVM calldata bytes.", + "example": "0x01020304" + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Asset" + } + }, + "kind": { + "type": "string", + "enum": [ + "custom" + ] + }, + "outputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Asset" + } + }, + "target": { + "$ref": "#/components/schemas/Address" + }, + "value": { + "$ref": "#/components/schemas/TokenAmount" + } + } + }, + "DateTime": { + "type": "string", + "description": "An ISO-8601 formatted date-time.", + "example": "1970-01-01T00:00:00.000Z" + }, + "Decimal": { + "type": "string", + "description": "An arbitrary-precision decimal value.", + "example": "13.37" + }, + "FeePolicy": { + "oneOf": [ + { + "$ref": "#/components/schemas/SurplusFee" + }, + { + "$ref": "#/components/schemas/PriceImprovement" + }, + { + "$ref": "#/components/schemas/VolumeFee" + } + ], + "description": "A fee policy that applies to an order" + }, + "ForeignLimitOrder": { + "type": "object", + "description": "A 0x-like limit order external to CoW Protocol.", + "required": [ + "kind", + "makerToken", + "takerToken", + "makerAmount", + "takerAmount", + "takerTokenFeeAmount" + ], + "properties": { + "kind": { + "type": "string", + "enum": [ + "limitOrder" + ] + }, + "makerAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "makerToken": { + "$ref": "#/components/schemas/Token" + }, + "takerAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "takerToken": { + "$ref": "#/components/schemas/Token" + }, + "takerTokenFeeAmount": { + "$ref": "#/components/schemas/TokenAmount" + } + } + }, + "Fulfillment": { + "type": "object", + "description": "A trade which fulfills an order from the auction.", + "required": [ + "kind", + "order" + ], + "properties": { + "executedAmount": { + "allOf": [ + { + "$ref": "#/components/schemas/TokenAmount" + } + ], + "description": "The amount of the order that was executed. This is denoted in 'sellToken' for sell orders, and 'buyToken' for buy orders." + }, + "fee": { + "type": "object", + "description": "The sell token amount that should be taken as a fee for this trade. This only gets returned for limit orders and only refers to the actual amount filled by the trade." + }, + "kind": { + "type": "string", + "enum": [ + "fulfillment" + ] + }, + "order": { + "allOf": [ + { + "$ref": "#/components/schemas/OrderUid" + } + ], + "description": "A reference by UID of the order to execute in a solution. The order must be included in the auction input." + } + } + }, + "I128": { + "type": "string", + "description": "128 bit signed integer in decimal notation.", + "example": "-1234567890" + }, + "I32": { + "type": "string", + "description": "32 bit signed integer in decimal notation.", + "example": "-12345" + }, + "Interaction": { + "allOf": [ + { + "type": "object", + "properties": { + "internalize": { + "type": "boolean", + "description": "A flag indicating that the interaction should be 'internalized', as specified by CIP-2." + } + } + }, + { + "oneOf": [ + { + "$ref": "#/components/schemas/LiquidityInteraction" + }, + { + "$ref": "#/components/schemas/CustomInteraction" + } + ] + } + ], + "description": "An interaction to execute as part of a settlement." + }, + "InteractionData": { + "type": "object", + "required": [ + "target", + "value", + "callData" + ], + "properties": { + "callData": { + "type": "string", + "example": "0x01020304" + }, + "target": { + "$ref": "#/components/schemas/Address" + }, + "value": { + "$ref": "#/components/schemas/TokenAmount" + } + } + }, + "JitOrder": { + "type": "object", + "description": "A just-in-time liquidity order included in a settlement.", + "required": [ + "sellToken", + "buyToken", + "receiver", + "sellAmount", + "buyAmount", + "validTo", + "appData", + "feeAmount", + "kind", + "partiallyFillable", + "sellTokenBalance", + "buyTokenBalance", + "signingScheme", + "signature" + ], + "properties": { + "appData": { + "$ref": "#/components/schemas/AppData" + }, + "buyAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "buyToken": { + "$ref": "#/components/schemas/Token" + }, + "buyTokenBalance": { + "$ref": "#/components/schemas/BuyTokenBalance" + }, + "feeAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "kind": { + "$ref": "#/components/schemas/OrderKind" + }, + "partiallyFillable": { + "type": "boolean" + }, + "receiver": { + "$ref": "#/components/schemas/Address" + }, + "sellAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "sellToken": { + "$ref": "#/components/schemas/Token" + }, + "sellTokenBalance": { + "$ref": "#/components/schemas/SellTokenBalance" + }, + "signature": { + "$ref": "#/components/schemas/Signature" + }, + "signingScheme": { + "$ref": "#/components/schemas/SigningScheme" + }, + "validTo": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "JitTrade": { + "type": "object", + "description": "A trade with a JIT order.", + "required": [ + "kind", + "order", + "executedAmount" + ], + "properties": { + "executedAmount": { + "allOf": [ + { + "$ref": "#/components/schemas/TokenAmount" + } + ], + "description": "The amount of the order that was executed. This is denoted in 'sellToken' for sell orders, and 'buyToken' for buy orders." + }, + "kind": { + "type": "string", + "enum": [ + "jit" + ] + }, + "order": { + "allOf": [ + { + "$ref": "#/components/schemas/JitOrder" + } + ], + "description": "The just-in-time liquidity order to execute in a solution." + } + } + }, + "LegacySigningScheme": { + "type": "string", + "enum": [ + "eip712", + "ethsign", + "eip1271", + "presign" + ] + }, + "Liquidity": { + "allOf": [ + { + "$ref": "#/components/schemas/LiquidityParameters" + }, + { + "type": "object", + "required": [ + "id", + "address", + "gasEstimate" + ], + "properties": { + "address": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "description": "A rough approximation of gas units required to use this liquidity on-chain." + }, + "gasEstimate": { + "allOf": [ + { + "$ref": "#/components/schemas/BigInt" + } + ], + "description": "A rough approximation of gas units required to use this liquidity on-chain." + }, + "id": { + "type": "string", + "description": "An opaque ID used for uniquely identifying the liquidity within a single auction (note that they are **not** guaranteed to be unique across auctions). This ID is used in the solution for matching interactions with the executed liquidity." + } + } + } + ], + "description": "On-chain liquidity that can be used in a solution. This liquidity is provided to facilitate onboarding new solvers. Additional liquidity that is not included in this set may still be used in solutions." + }, + "LiquidityInteraction": { + "type": "object", + "description": "Interaction representing the execution of liquidity that was passed in with the auction.", + "required": [ + "kind", + "id", + "inputToken", + "outputToken", + "inputAmount", + "outputAmount" + ], + "properties": { + "id": { + "type": "string", + "description": "The ID of executed liquidity provided in the auction input." + }, + "inputAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "inputToken": { + "$ref": "#/components/schemas/Token" + }, + "kind": { + "type": "string", + "enum": [ + "liquidity" + ] + }, + "outputAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "outputToken": { + "$ref": "#/components/schemas/Token" + } + } + }, + "LiquidityParameters": { + "oneOf": [ + { + "$ref": "#/components/schemas/ConstantProductPool" + }, + { + "$ref": "#/components/schemas/WeightedProductPool" + }, + { + "$ref": "#/components/schemas/StablePool" + }, + { + "$ref": "#/components/schemas/ConcentratedLiquidityPool" + }, + { + "$ref": "#/components/schemas/ForeignLimitOrder" + } + ] + }, + "NativePrice": { + "type": "string", + "description": "The price in wei of the native token (Ether on Mainnet for example) to buy\n10**18 of a token.", + "example": "1234567890" + }, + "Order": { + "type": "object", + "description": "CoW Protocol order information relevant to execution.", + "required": [ + "uid", + "sellToken", + "buyToken", + "sellAmount", + "fullSellAmount", + "buyAmount", + "fullBuyAmount", + "validTo", + "kind", + "receiver", + "owner", + "partiallyFillable", + "preInteractions", + "postInteractions", + "sellTokenSource", + "buyTokenDestination", + "class", + "appData", + "signingScheme", + "signature" + ], + "properties": { + "appData": { + "$ref": "#/components/schemas/AppData" + }, + "buyAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "buyToken": { + "$ref": "#/components/schemas/Token" + }, + "buyTokenDestination": { + "$ref": "#/components/schemas/BuyTokenDestination" + }, + "class": { + "$ref": "#/components/schemas/OrderClass" + }, + "feePolicies": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FeePolicy" + }, + "nullable": true + }, + "fullBuyAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "fullSellAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "kind": { + "$ref": "#/components/schemas/OrderKind" + }, + "owner": { + "$ref": "#/components/schemas/Address" + }, + "partiallyFillable": { + "type": "boolean", + "description": "Whether or not this order can be partially filled. If this is false,\nthen the order is a \"fill-or-kill\" order, meaning it needs to be\ncompletely filled or not at all." + }, + "postInteractions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InteractionData" + } + }, + "preInteractions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InteractionData" + } + }, + "receiver": { + "$ref": "#/components/schemas/Address" + }, + "sellAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "sellToken": { + "$ref": "#/components/schemas/Token" + }, + "sellTokenSource": { + "$ref": "#/components/schemas/SellTokenSource" + }, + "signature": { + "$ref": "#/components/schemas/Signature" + }, + "signingScheme": { + "$ref": "#/components/schemas/LegacySigningScheme" + }, + "uid": { + "$ref": "#/components/schemas/OrderUid" + }, + "validTo": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + }, + "OrderClass": { + "type": "string", + "description": "How the CoW Protocol order was classified.", + "enum": [ + "market", + "limit", + "liquidity" + ] + }, + "OrderKind": { + "type": "string", + "description": "How the CoW Protocol order was classified.", + "enum": [ + "sell", + "buy" + ] + }, + "OrderUid": { + "type": "string", + "description": "Unique identifier for the order. Order UIDs are 56 bytes long, where bytes\n[0, 32) represent the order digest used for signing, bytes [32, 52)\nrepresent the owner address and bytes [52, 56) represent the order's\n`validTo` field.", + "example": "0x30cff40d9f60caa68a37f0ee73253ad6ad72b45580c945fe3ab67596476937197854163b1b0d24e77dca702b97b5cc33e0f83dcb626122a6" + }, + "PriceImprovement": { + "type": "object", + "description": "A cut from the price improvement over the best quote is taken as a protocol fee.", + "properties": { + "factor": { + "type": "number", + "description": "The factor of the user surplus that the protocol will request from the solver after settling the order", + "example": 0.5 + }, + "kind": { + "type": "string", + "enum": [ + "priceImprovement" + ] + }, + "maxVolumeFactor": { + "type": "number", + "description": "Never charge more than that percentage of the order volume.", + "example": 0.01, + "maximum": 0.99999, + "minimum": 0 + }, + "quote": { + "$ref": "#/components/schemas/Quote" + } + } + }, + "Quote": { + "type": "object", + "required": [ + "sellAmount", + "buyAmount", + "fee" + ], + "properties": { + "buyAmount": { + "$ref": "#/components/schemas/TokenAmount" + }, + "fee": { + "$ref": "#/components/schemas/TokenAmount" + }, + "sellAmount": { + "$ref": "#/components/schemas/TokenAmount" + } + } + }, + "SellTokenBalance": { + "type": "string", + "description": "Where should the sell token be drawn from?", + "enum": [ + "erc20", + "internal", + "external" + ] + }, + "SellTokenSource": { + "type": "string", + "description": "Source from which the sellAmount should be drawn upon order fulfillment", + "enum": [ + "erc20", + "external", + "internal" + ] + }, + "Signature": { + "type": "string", + "description": "Signature bytes.", + "example": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "SigningScheme": { + "type": "string", + "description": "How was the order signed?", + "enum": [ + "eip712", + "ethSign", + "preSign", + "eip1271" + ] + }, + "Solution": { + "type": "object", + "description": "A computed solution for a given auction.", + "required": [ + "id", + "prices", + "trades", + "interactions" + ], + "properties": { + "gas": { + "type": "integer", + "format": "int64", + "description": "How many units of gas this solution is estimated to cost.", + "nullable": true, + "minimum": 0 + }, + "id": { + "type": "number", + "format": "int64", + "description": "An opaque identifier for the solution. This is a solver generated number\nthat is unique across multiple solutions within the auction." + }, + "interactions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Interaction" + }, + "description": "Interactions to encode within a settlement." + }, + "prices": { + "type": "object", + "description": "A clearing price map of token address to price. The price can have\narbitrary denomination.", + "additionalProperties": { + "$ref": "#/components/schemas/U256" + } + }, + "trades": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Trade" + }, + "description": "CoW Protocol order trades included in the solution." + } + } + }, + "StablePool": { + "type": "object", + "description": "A Curve-like stable pool of N tokens.", + "required": [ + "kind", + "tokens", + "amplificationParameter", + "fee", + "balancer_pool_id" + ], + "properties": { + "amplificationParameter": { + "$ref": "#/components/schemas/Decimal" + }, + "balancer_pool_id": { + "$ref": "#/components/schemas/BalancerPoolId" + }, + "fee": { + "$ref": "#/components/schemas/Decimal" + }, + "kind": { + "type": "string", + "enum": [ + "stable" + ] + }, + "tokens": { + "type": "object", + "description": "A mapping of token address to token balance and scaling rate.", + "additionalProperties": { + "allOf": [ + { + "$ref": "#/components/schemas/TokenReserve" + }, + { + "type": "object", + "required": [ + "scalingFactor" + ], + "properties": { + "scalingFactor": { + "$ref": "#/components/schemas/Decimal" + } + } + } + ] + } + } + } + }, + "SurplusFee": { + "type": "object", + "description": "If the order receives more than limit price, pay the protocol a factor of the difference.", + "properties": { + "factor": { + "type": "number", + "description": "The factor of the user surplus that the protocol will request from the solver after settling the order", + "example": 0.5 + }, + "kind": { + "type": "string", + "enum": [ + "surplus" + ] + }, + "maxVolumeFactor": { + "type": "number", + "description": "Never charge more than that percentage of the order volume.", + "example": 0.05, + "maximum": 0.99999, + "minimum": 0 + } + } + }, + "Token": { + "type": "string", + "description": "An ERC20 token address.", + "example": "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB" + }, + "TokenAmount": { + "type": "string", + "description": "Amount of an ERC20 token. 256 bit unsigned integer in decimal notation.", + "example": "1234567890" + }, + "TokenInfo": { + "type": "object", + "description": "Information about an ERC20 token.", + "required": [ + "availableBalance", + "trusted" + ], + "properties": { + "availableBalance": { + "allOf": [ + { + "$ref": "#/components/schemas/TokenAmount" + } + ], + "description": "The balance held by the Settlement contract that is available during a settlement." + }, + "decimals": { + "type": "integer", + "description": "The ERC20.decimals value for this token. This may be missing for ERC20 tokens that don't implement the optional metadata extension." + }, + "referencePrice": { + "allOf": [ + { + "$ref": "#/components/schemas/NativePrice" + } + ], + "description": "The reference price of this token for the auction used for scoring. This price is only included for tokens for which there are CoW Protocol orders." + }, + "symbol": { + "type": "string", + "description": "The ERC20.symbol value for this token. This may be missing for ERC20 tokens that don't implement the optional metadata extension." + }, + "trusted": { + "type": "boolean", + "description": "A flag which indicates that solvers are allowed to perform gas cost optimizations for this token by not routing the trades via an AMM, and instead use its available balances, as specified by CIP-2." + } + } + }, + "TokenReserve": { + "type": "object", + "required": [ + "balance" + ], + "properties": { + "balance": { + "$ref": "#/components/schemas/TokenAmount" + } + }, + "additionalProperties": false + }, + "Trade": { + "oneOf": [ + { + "$ref": "#/components/schemas/Fulfillment" + }, + { + "$ref": "#/components/schemas/JitTrade" + } + ], + "description": "A trade for a CoW Protocol order included in a solution." + }, + "U128": { + "type": "string", + "description": "128 bit unsigned integer in decimal notation.", + "example": "1234567890" + }, + "U256": { + "type": "string", + "description": "256 bit unsigned integer in decimal notation.", + "example": "1234567890" + }, + "VolumeFee": { + "type": "object", + "properties": { + "factor": { + "type": "number", + "description": "The fraction of the order's volume that the protocol will request from the solver after settling the order.", + "example": 0.5 + }, + "kind": { + "type": "string", + "enum": [ + "volume" + ] + } + } + }, + "WeightedProductPool": { + "type": "object", + "description": "A Balancer-like weighted product liquidity pool of N tokens.", + "required": [ + "kind", + "tokens", + "fee", + "balancer_pool_id" + ], + "properties": { + "balancer_pool_id": { + "$ref": "#/components/schemas/BalancerPoolId" + }, + "fee": { + "$ref": "#/components/schemas/Decimal" + }, + "kind": { + "type": "string", + "enum": [ + "weightedProduct" + ] + }, + "tokens": { + "type": "object", + "description": "A mapping of token address to its reserve amounts with weights.", + "additionalProperties": { + "allOf": [ + { + "$ref": "#/components/schemas/TokenReserve" + }, + { + "type": "object", + "required": [ + "weight" + ], + "properties": { + "scalingFactor": { + "$ref": "#/components/schemas/Decimal" + }, + "weight": { + "$ref": "#/components/schemas/Decimal" + } + } + } + ] + } + }, + "version": { + "type": "string", + "enum": [ + "v0", + "v3Plus" + ] + } + } + } + } + } +} \ No newline at end of file diff --git a/crates/solvers/openapi.yml b/crates/solvers/openapi.yml deleted file mode 100644 index 25d988e21b..0000000000 --- a/crates/solvers/openapi.yml +++ /dev/null @@ -1,872 +0,0 @@ -openapi: 3.0.3 -info: - title: Solver Engine API - description: | - The API implemented by solver engines interacting with the reference driver - implementation. - version: 0.0.1 - -paths: - /solve: - post: - description: | - Solve the passed in auction instance. - requestBody: - required: true - content: - application/json: - schema: - $ref: "#/components/schemas/Auction" - responses: - 200: - description: Auction successfully solved. - content: - application/json: - schema: - type: object - required: - - solutions - properties: - solutions: - description: | - Proposed solutions to settle some of the orders in the auction. - type: array - items: - $ref: "#/components/schemas/Solution" - 400: - description: There is something wrong with the request. - 429: - description: The solver cannot keep up. It is too busy to handle more requests. - 500: - description: Something went wrong when handling the request. - /notify: - post: - description: | - Receive a status notification about a previously provided solution. - requestBody: - required: true - content: - application/json: - schema: - type: object - additionalProperties: true - description: | - A notification that informs the solver how its solution performed in the auction. - Depending on the notification type additional meta data may be attached but this - is not guaranteed to be stable. - properties: - auctionId: - description: | - The auction ID of the auction that the solution was provided - for. - type: string - solutionId: - description: | - The solution ID within the auction for which the notification applies - type: number - kind: - description: | - The kind of notification. - type: string - enum: - [ - timeout, - emptySolution, - duplicatedSolutionId, - simulationFailed, - invalidClearingPrices, - missingPrice, - invalidExecutedAmount, - nonBufferableTokensUsed, - solverAccountInsufficientBalance, - success, - revert, - driverError, - cancelled, - fail, - postprocessingTimedOut, - ] - responses: - 200: - description: notification successfully received. - -components: - schemas: - Address: - description: | - An Ethereum public address. - type: string - example: "0x0000000000000000000000000000000000000000" - - Token: - description: | - An ERC20 token address. - type: string - example: "0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB" - - TokenAmount: - description: | - Amount of an ERC20 token. 256 bit unsigned integer in decimal notation. - type: string - example: "1234567890" - - U256: - description: | - 256 bit unsigned integer in decimal notation. - type: string - example: "1234567890" - - U128: - description: | - 128 bit unsigned integer in decimal notation. - type: string - example: "1234567890" - - I128: - description: | - 128 bit signed integer in decimal notation. - type: string - example: "-1234567890" - - I32: - description: | - 32 bit signed integer in decimal notation. - type: number - example: -12345 - - BalancerPoolId: - description: | - A hex-encoded 32 byte string containing the pool address (0..20), the pool specialization (20..22) and the poolnonce (22..32). - type: string - example: "0xc88c76dd8b92408fe9bea1a54922a31e232d873c0002000000000000000005b2" - - BigInt: - description: | - An arbitrary-precision integer value. - type: string - example: "1234567890" - - Decimal: - description: | - An arbitrary-precision decimal value. - type: string - example: "13.37" - - NativePrice: - description: | - The price in wei of the native token (Ether on Mainnet for example) to - buy 10**18 of a token. - type: string - example: "1234567890" - - DateTime: - description: An ISO-8601 formatted date-time. - type: string - example: "1970-01-01T00:00:00.000Z" - - TokenInfo: - description: | - Information about a token relevant to the auction. - type: object - required: - - trusted - - availableBalance - properties: - decimals: - description: | - The ERC20.decimals value for this token. This may be missing for - ERC20 tokens that don't implement the optional metadata extension. - type: integer - symbol: - description: | - The ERC20.symbol value for this token. This may be missing for ERC20 - tokens that don't implement the optional metadata extension. - type: string - referencePrice: - description: | - The reference price of this token for the auction used for scoring. - This price is only included for tokens for which there are CoW - Protocol orders. - allOf: - - $ref: "#/components/schemas/NativePrice" - availableBalance: - description: | - The balance held by the Settlement contract that is available - during a settlement. - allOf: - - $ref: "#/components/schemas/TokenAmount" - trusted: - description: | - A flag which indicates that solvers are allowed to perform gas cost - optimizations for this token by not routing the trades via an AMM, - and instead use its available balances, as specified by CIP-2. - type: boolean - - Asset: - description: | - A token address with an amount. - type: object - required: - - token - - amount - properties: - token: - $ref: "#/components/schemas/Token" - amount: - $ref: "#/components/schemas/TokenAmount" - - OrderUid: - description: | - Unique identifier for the order. Order UIDs are 56 bytes long, where - bytes [0, 32) represent the order digest used for signing, bytes - [32, 52) represent the owner address and bytes [52, 56) represent the - order's `validTo` field. - type: string - example: "0x30cff40d9f60caa68a37f0ee73253ad6ad72b45580c945fe3ab67596476937197854163b1b0d24e77dca702b97b5cc33e0f83dcb626122a6" - - OrderKind: - description: | - The trading side of the order. - type: string - enum: [ sell, buy ] - - OrderClass: - description: | - How the CoW Protocol order was classified. - type: string - enum: [ market, limit, liquidity ] - - AppData: - description: | - 32 bytes of arbitrary application specific data that can be added to an - order. This can also be used to ensure uniqueness between two orders - with otherwise the exact same parameters. - example: "0x0000000000000000000000000000000000000000000000000000000000000000" - - SellTokenBalance: - description: | - Where should the sell token be drawn from? - type: string - enum: [ erc20, internal, external ] - - BuyTokenBalance: - description: | - Where should the buy token be transferred to? - type: string - enum: [ erc20, internal ] - - SigningScheme: - description: | - How was the order signed? - type: string - enum: [ eip712, ethSign, preSign, eip1271 ] - - Signature: - description: | - Signature bytes. - type: string - example: "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - - Order: - description: | - CoW Protocol order information relevant to execution. - type: object - required: - - uid - - sellToken - - buyToken - - sellAmount - - buyAmount - - kind - - partiallyFillable - - class - properties: - uid: - $ref: "#/components/schemas/OrderUid" - sellToken: - $ref: "#/components/schemas/Token" - buyToken: - $ref: "#/components/schemas/Token" - sellAmount: - $ref: "#/components/schemas/TokenAmount" - buyAmount: - $ref: "#/components/schemas/TokenAmount" - kind: - $ref: "#/components/schemas/OrderKind" - partiallyFillable: - description: | - Whether or not this order can be partially filled. If this is false, - then the order is a "fill-or-kill" order, meaning it needs to be - completely filled or not at all. - type: boolean - class: - $ref: "#/components/schemas/OrderClass" - feePolicies: - description: | - Any protocol fee policies that apply to the order. - type: array - items: - $ref: "#/components/schemas/FeePolicy" - FeePolicy: - description: | - A fee policy that applies to an order. - type: object - oneOf: - - $ref: "#/components/schemas/SurplusFee" - - $ref: "#/components/schemas/PriceImprovement" - - $ref: "#/components/schemas/VolumeFee" - SurplusFee: - description: | - If the order receives more than limit price, pay the protocol a factor of the difference. - type: object - properties: - kind: - type: string - enum: [ "surplus" ] - maxVolumeFactor: - description: Never charge more than that percentage of the order volume. - type: number - minimum: 0.0 - maximum: 0.99999 - example: 0.05 - factor: - description: The factor of the user surplus that the protocol will request from the solver after settling the order - type: number - example: 0.5 - PriceImprovement: - description: | - A cut from the price improvement over the best quote is taken as a protocol fee. - type: object - properties: - kind: - type: string - enum: [ "priceImprovement" ] - maxVolumeFactor: - description: Never charge more than that percentage of the order volume. - type: number - example: 0.01 - factor: - description: The factor of the user surplus that the protocol will request from the solver after settling the order - type: number - example: 0.5 - quote: - $ref: "#/components/schemas/Quote" - VolumeFee: - type: object - properties: - kind: - type: string - enum: [ "volume" ] - factor: - description: The fraction of the order's volume that the protocol will request from the solver after settling the order. - type: number - example: 0.5 - Quote: - type: object - properties: - sell_amount: - $ref: "#/components/schemas/TokenAmount" - buy_amount: - $ref: "#/components/schemas/TokenAmount" - TokenReserve: - description: | - A reserve of tokens in an on-chain liquidity pool. - type: object - required: - - balance - properties: - balance: - $ref: "#/components/schemas/TokenAmount" - - ConstantProductPool: - description: | - A UniswapV2-like constant product liquidity pool for a token pair. - type: object - required: - - kind - - tokens - - fee - - router - properties: - kind: - type: string - enum: [ constantProduct ] - tokens: - description: | - A mapping of token address to its reserve amounts. - type: object - additionalProperties: - $ref: "#/components/schemas/TokenReserve" - fee: - $ref: "#/components/schemas/Decimal" - router: - $ref: "#/components/schemas/Address" - - WeightedProductPool: - description: | - A Balancer-like weighted product liquidity pool of N tokens. - type: object - required: - - kind - - tokens - - fee - - balancer_pool_id - properties: - kind: - type: string - enum: [ weightedProduct ] - tokens: - description: | - A mapping of token address to its reserve amounts with weights. - type: object - additionalProperties: - allOf: - - $ref: "#/components/schemas/TokenReserve" - - type: object - required: - - weight - properties: - scalingFactor: - $ref: "#/components/schemas/Decimal" - weight: - $ref: "#/components/schemas/Decimal" - fee: - $ref: "#/components/schemas/Decimal" - version: - type: string - enum: [ "v0", "v3Plus" ] - balancer_pool_id: - $ref: "#/components/schemas/BalancerPoolId" - - StablePool: - description: | - A Curve-like stable pool of N tokens. - type: object - required: - - kind - - tokens - - amplificationParameter - - fee - - balancer_pool_id - properties: - kind: - type: string - enum: [ stable ] - tokens: - description: | - A mapping of token address to token balance and scaling rate. - type: object - additionalProperties: - allOf: - - $ref: "#/components/schemas/TokenReserve" - - type: object - required: - - scalingFactor - properties: - scalingFactor: - $ref: "#/components/schemas/Decimal" - amplificationParameter: - $ref: "#/components/schemas/Decimal" - fee: - $ref: "#/components/schemas/Decimal" - balancer_pool_id: - $ref: "#/components/schemas/BalancerPoolId" - - ConcentratedLiquidityPool: - description: | - A UniswapV3-like concentrated liquidity pool of 2 tokens. - type: object - required: - - kind - - tokens - - sqrtPrice - - liquidity - - tick - - liquidityNet - - fee - - router - properties: - kind: - type: string - enum: [ concentratedLiquidity ] - tokens: - type: array - items: - $ref: "#/components/schemas/Token" - sqrtPrice: - $ref: "#/components/schemas/U256" - liquidity: - $ref: "#/components/schemas/U128" - tick: - $ref: "#/components/schemas/I32" - liquidityNet: - description: | - A map of tick indices to their liquidity values. - type: object - additionalProperties: - $ref: "#/components/schemas/I128" - fee: - $ref: "#/components/schemas/Decimal" - router: - $ref: "#/components/schemas/Address" - - ForeignLimitOrder: - description: | - A 0x-like limit order external to CoW Protocol. - type: object - required: - - kind - - makerToken - - takerToken - - makerAmount - - takerAmount - - takerTokenFeeAmount - properties: - kind: - type: string - enum: [ limitOrder ] - makerToken: - $ref: "#/components/schemas/Token" - takerToken: - $ref: "#/components/schemas/Token" - makerAmount: - $ref: "#/components/schemas/TokenAmount" - takerAmount: - $ref: "#/components/schemas/TokenAmount" - takerTokenFeeAmount: - $ref: "#/components/schemas/TokenAmount" - - LiquidityParameters: - oneOf: - - $ref: "#/components/schemas/ConstantProductPool" - - $ref: "#/components/schemas/WeightedProductPool" - - $ref: "#/components/schemas/StablePool" - - $ref: "#/components/schemas/ConcentratedLiquidityPool" - - $ref: "#/components/schemas/ForeignLimitOrder" - - Liquidity: - description: | - On-chain liquidity that can be used in a solution. This liquidity is - provided to facilitate onboarding new solvers. Additional liquidity that - is not included in this set may still be used in solutions. - allOf: - - $ref: "#/components/schemas/LiquidityParameters" - - type: object - required: - - id - - address - - gasEstimate - properties: - id: - description: | - An opaque ID used for uniquely identifying the liquidity within - a single auction (note that they are **not** guaranteed to be - unique across auctions). This ID is used in the solution for - matching interactions with the executed liquidity. - type: string - address: - description: | - The Ethereum public address of the liquidity. The actual address - that is specified is dependent on the kind of liquidity. - allOf: - - $ref: "#/components/schemas/Address" - gasEstimate: - description: | - A rough approximation of gas units required to use this - liquidity on-chain. - allOf: - - $ref: "#/components/schemas/BigInt" - - Auction: - description: | - The abstract auction to be solved by the searcher. - type: object - required: - - tokens - - orders - - liquidity - - effectiveGasPrice - - deadline - properties: - id: - description: | - An opaque identifier for the auction. Will be set to `null` for - requests that are not part of an auction (when quoting token prices - for example). - type: string - tokens: - description: | - A map of token addresses to token information. - type: object - additionalProperties: - $ref: "#/components/schemas/TokenInfo" - orders: - description: | - The solvable orders included in the auction. - type: array - items: - $ref: "#/components/schemas/Order" - liquidity: - description: | - On-chain liquidity that can be used by the solution. - type: array - items: - $ref: "#/components/schemas/Liquidity" - effectiveGasPrice: - description: | - The current estimated gas price that will be paid when executing a - settlement. Additionally, this is the gas price that is multiplied - with a settlement's gas estimate for solution scoring. - allOf: - - $ref: "#/components/schemas/TokenAmount" - deadline: - description: | - The deadline by which a solution to the auction is required. - Requests that go beyond this deadline are expected to be cancelled - by the caller. - allOf: - - $ref: "#/components/schemas/DateTime" - - JitOrder: - description: | - A just-in-time liquidity order included in a settlement. - type: object - required: - - sellToken - - buyToken - - receiver - - sellAmount - - buyAmount - - validTo - - appData - - feeAmount - - kind - - partiallyFillable - - sellTokenBalance - - buyTokenBalance - - signingScheme - - signature - properties: - sellToken: - $ref: "#/components/schemas/Token" - buyToken: - $ref: "#/components/schemas/Token" - receiver: - $ref: "#/components/schemas/Address" - sellAmount: - $ref: "#/components/schemas/TokenAmount" - buyAmount: - $ref: "#/components/schemas/TokenAmount" - validTo: - type: integer - appData: - $ref: "#/components/schemas/AppData" - feeAmount: - $ref: "#/components/schemas/TokenAmount" - kind: - $ref: "#/components/schemas/OrderKind" - partiallyFillable: - type: boolean - sellTokenBalance: - $ref: "#/components/schemas/SellTokenBalance" - buyTokenBalance: - $ref: "#/components/schemas/BuyTokenBalance" - signingScheme: - $ref: "#/components/schemas/SigningScheme" - signature: - $ref: "#/components/schemas/Signature" - - Fulfillment: - description: | - A trade which fulfills an order from the auction. - type: object - required: - - kind - - order - properties: - kind: - type: string - enum: [ fulfillment ] - order: - description: | - A reference by UID of the order to execute in a solution. The order - must be included in the auction input. - allOf: - - $ref: "#/components/schemas/OrderUid" - fee: - description: | - The sell token amount that should be taken as a fee for this - trade. This only gets returned for limit orders and only refers - to the actual amount filled by the trade. - executedAmount: - description: | - The amount of the order that was executed. This is denoted in - "sellToken" for sell orders, and "buyToken" for buy orders. - allOf: - - $ref: "#/components/schemas/TokenAmount" - - JitTrade: - description: | - A trade with a JIT order. - required: - - kind - - order - - executedAmount - properties: - kind: - type: string - enum: [ jit ] - executedAmount: - description: | - The amount of the order that was executed. This is denoted in - "sellToken" for sell orders, and "buyToken" for buy orders. - allOf: - - $ref: "#/components/schemas/TokenAmount" - order: - description: | - The just-in-time liquidity order to execute in a solution. - allOf: - - $ref: "#/components/schemas/JitOrder" - Trade: - description: | - A trade for a CoW Protocol order included in a solution. - oneOf: - - $ref: "#/components/schemas/Fulfillment" - - $ref: "#/components/schemas/JitTrade" - - LiquidityInteraction: - description: | - Interaction representing the execution of liquidity that was passed in - with the auction. - type: object - required: - - kind - - id - - inputToken - - outputToken - - inputAmount - - outputAmount - properties: - kind: - type: string - enum: [ liquidity ] - id: - description: | - The ID of executed liquidity provided in the auction input. - type: number - inputToken: - $ref: "#/components/schemas/Token" - outputToken: - $ref: "#/components/schemas/Token" - inputAmount: - $ref: "#/components/schemas/TokenAmount" - outputAmount: - $ref: "#/components/schemas/TokenAmount" - - Allowance: - description: | - An ERC20 allowance from the settlement contract to some spender that is - required for a custom interaction. - type: object - required: - - token - - spender - - minAmount - properties: - token: - $ref: "#/components/schemas/Token" - spender: - $ref: "#/components/schemas/Address" - amount: - $ref: "#/components/schemas/TokenAmount" - - CustomInteraction: - description: | - A searcher-specified custom interaction to be included in the final - settlement. - type: object - required: - - kind - - target - - value - - calldata - - inputs - - outputs - properties: - kind: - type: string - enum: [ custom ] - target: - $ref: "#/components/schemas/Address" - value: - $ref: "#/components/schemas/TokenAmount" - calldata: - description: | - The EVM calldata bytes. - type: string - example: "0x01020304" - allowances: - description: | - ERC20 allowances that are required for this custom interaction. - type: array - items: - $ref: "#/components/schemas/Allowance" - inputs: - type: array - items: - $ref: "#/components/schemas/Asset" - outputs: - type: array - items: - $ref: "#/components/schemas/Asset" - - Interaction: - description: | - An interaction to execute as part of a settlement. - allOf: - - type: object - properties: - internalize: - description: | - A flag indicating that the interaction should be "internalized", - as specified by CIP-2. - type: boolean - - oneOf: - - $ref: "#/components/schemas/LiquidityInteraction" - - $ref: "#/components/schemas/CustomInteraction" - - Solution: - description: | - A computed solution for a given auction. - type: object - required: - - id - - prices - - trades - - interactions - properties: - id: - description: An opaque identifier for the solution. This is a solver generated number that is unique across multiple solutions within the auction. - type: number - prices: - description: | - A clearing price map of token address to price. The price can have - arbitrary denomination. - type: object - additionalProperties: - $ref: "#/components/schemas/U256" - trades: - description: | - CoW Protocol order trades included in the solution. - type: array - items: - $ref: "#/components/schemas/Trade" - interactions: - description: | - Interactions to encode within a settlement. - type: array - items: - $ref: "#/components/schemas/Interaction" - gas: - type: integer - description: How many units of gas this solution is estimated to cost. diff --git a/crates/solvers/src/api/mod.rs b/crates/solvers/src/api/mod.rs index 63521187e6..756d4e9011 100644 --- a/crates/solvers/src/api/mod.rs +++ b/crates/solvers/src/api/mod.rs @@ -2,8 +2,10 @@ use { crate::domain::solver::Solver, + serde_json::Error, std::{future::Future, net::SocketAddr, sync::Arc}, tokio::sync::oneshot, + utoipa::OpenApi, }; mod routes; @@ -46,3 +48,74 @@ impl Api { server.with_graceful_shutdown(shutdown).await } } + +// migrate to utoipauto once the issue is solved https://github.com/ProbablyClem/utoipauto/issues/23 +pub fn generate_openapi_json() -> Result { + #[derive(OpenApi)] + #[openapi( + paths(routes::solve::solve, routes::notify::notify,), + components(schemas( + solvers_dto::auction::Auction, + solvers_dto::auction::TokenInfo, + solvers_dto::auction::NativePrice, + solvers_dto::auction::DateTime, + solvers_dto::auction::Liquidity, + solvers_dto::auction::LiquidityParameters, + solvers_dto::auction::ConstantProductPool, + solvers_dto::auction::WeightedProductPool, + solvers_dto::auction::StablePool, + solvers_dto::auction::ConcentratedLiquidityPool, + solvers_dto::auction::ForeignLimitOrder, + solvers_dto::auction::TokenReserve, + solvers_dto::auction::BalancerPoolId, + solvers_dto::auction::Decimal, + solvers_dto::auction::U256Schema, + solvers_dto::auction::U128, + solvers_dto::auction::I128, + solvers_dto::auction::I32, + solvers_dto::auction::BigInt, + solvers_dto::auction::Order, + solvers_dto::auction::OrderUid, + solvers_dto::auction::FeePolicy, + solvers_dto::auction::Quote, + solvers_dto::auction::OrderClass, + solvers_dto::auction::OrderKind, + solvers_dto::auction::SurplusFee, + solvers_dto::auction::PriceImprovement, + solvers_dto::auction::VolumeFee, + solvers_dto::auction::SellTokenSource, + solvers_dto::auction::InteractionData, + solvers_dto::auction::BuyTokenDestination, + solvers_dto::auction::SellTokenSource, + solvers_dto::auction::LegacySigningScheme, + solvers_dto::solution::Solution, + solvers_dto::solution::Interaction, + solvers_dto::solution::CustomInteraction, + solvers_dto::solution::LiquidityInteraction, + solvers_dto::solution::Allowance, + solvers_dto::solution::Asset, + solvers_dto::solution::Trade, + solvers_dto::solution::Fulfillment, + solvers_dto::solution::JitTrade, + solvers_dto::solution::JitOrder, + solvers_dto::solution::BuyTokenBalance, + solvers_dto::solution::SellTokenBalance, + solvers_dto::solution::SigningScheme, + solvers_dto::common::Address, + solvers_dto::common::AppData, + solvers_dto::common::Signature, + solvers_dto::common::TokenAmount, + solvers_dto::common::Token, + )), + info( + description = "The API implemented by solver engines interacting with the reference \ + driver implementation.", + title = "Solver Engine API", + version = "0.1.0", + license(name = "MIT OR Apache-2.0") + ) + )] + pub struct ApiDoc; + + ApiDoc::openapi().to_pretty_json() +} diff --git a/crates/solvers/src/api/routes/mod.rs b/crates/solvers/src/api/routes/mod.rs index 39ab3d4f4f..2338242767 100644 --- a/crates/solvers/src/api/routes/mod.rs +++ b/crates/solvers/src/api/routes/mod.rs @@ -2,8 +2,8 @@ use serde::Serialize; mod healthz; mod metrics; -mod notify; -mod solve; +pub mod notify; +pub mod solve; pub(super) use {healthz::healthz, metrics::metrics, notify::notify, solve::solve}; diff --git a/crates/solvers/src/api/routes/notify/mod.rs b/crates/solvers/src/api/routes/notify/mod.rs index e989fe862d..c99f738109 100644 --- a/crates/solvers/src/api/routes/notify/mod.rs +++ b/crates/solvers/src/api/routes/notify/mod.rs @@ -2,6 +2,15 @@ use {crate::domain::solver::Solver, std::sync::Arc, tracing::Instrument}; mod dto; +/// Receive a status notification about a previously provided solution. +#[utoipa::path( + post, + path = "/notify", + request_body = inline(dto::Notification), + responses( + (status = 200, description = "Notification successfully received."), + ), +)] pub async fn notify( state: axum::extract::State>, notification: axum::extract::Json, diff --git a/crates/solvers/src/api/routes/solve/dto/auction.rs b/crates/solvers/src/api/routes/solve/dto/auction.rs index ea2a357740..dc9918201e 100644 --- a/crates/solvers/src/api/routes/solve/dto/auction.rs +++ b/crates/solvers/src/api/routes/solve/dto/auction.rs @@ -50,13 +50,13 @@ pub fn to_domain(auction: &Auction) -> Result { amount: order.buy_amount, }, side: match order.kind { - Kind::Buy => order::Side::Buy, - Kind::Sell => order::Side::Sell, + OrderKind::Buy => order::Side::Buy, + OrderKind::Sell => order::Side::Sell, }, class: match order.class { - Class::Market => order::Class::Market, - Class::Limit => order::Class::Limit, - Class::Liquidity => order::Class::Liquidity, + OrderClass::Market => order::Class::Market, + OrderClass::Limit => order::Class::Limit, + OrderClass::Liquidity => order::Class::Liquidity, }, partially_fillable: order.partially_fillable, }) diff --git a/crates/solvers/src/api/routes/solve/dto/solution.rs b/crates/solvers/src/api/routes/solve/dto/solution.rs index e029269515..cf799d4f17 100644 --- a/crates/solvers/src/api/routes/solve/dto/solution.rs +++ b/crates/solvers/src/api/routes/solve/dto/solution.rs @@ -50,8 +50,8 @@ pub fn from_domain(solutions: &[solution::Solution]) -> super::Solutions { app_data: trade.order.app_data.0, fee_amount: 0.into(), kind: match trade.order.side { - crate::domain::order::Side::Buy => Kind::Buy, - crate::domain::order::Side::Sell => Kind::Sell, + crate::domain::order::Side::Buy => OrderKind::Buy, + crate::domain::order::Side::Sell => OrderKind::Sell, }, partially_fillable: trade.order.partially_fillable, sell_token_balance: SellTokenBalance::Erc20, diff --git a/crates/solvers/src/api/routes/solve/mod.rs b/crates/solvers/src/api/routes/solve/mod.rs index 3fd9d8834e..e7ace8054d 100644 --- a/crates/solvers/src/api/routes/solve/mod.rs +++ b/crates/solvers/src/api/routes/solve/mod.rs @@ -4,6 +4,18 @@ mod dto; use {crate::domain::solver::Solver, std::sync::Arc}; +/// Solve the passed in auction instance. +#[utoipa::path( + post, + path = "/solve", + request_body = Auction, + responses( + (status = 200, description = "Auction successfully solved.", body = inline(dto::Solutions)), + (status = 400, description = "There is something wrong with the request."), + (status = 429, description = "The solver cannot keep up. It is too busy to handle more requests."), + (status = 500, description = "Something went wrong when handling the request.") + ), +)] pub async fn solve( state: axum::extract::State>, auction: axum::extract::Json, diff --git a/crates/solvers/src/lib.rs b/crates/solvers/src/lib.rs index 23c05bfa77..ecce6443ce 100644 --- a/crates/solvers/src/lib.rs +++ b/crates/solvers/src/lib.rs @@ -11,4 +11,7 @@ mod run; mod tests; mod util; -pub use self::run::{run, start}; +pub use { + self::run::{run, start}, + api::generate_openapi_json, +};