Skip to content

Add v1/orders POST endpoint to get orders in batches#4048

Merged
m-sz merged 55 commits intomainfrom
get-orders-by-uid
Feb 26, 2026
Merged

Add v1/orders POST endpoint to get orders in batches#4048
m-sz merged 55 commits intomainfrom
get-orders-by-uid

Conversation

@m-sz
Copy link
Contributor

@m-sz m-sz commented Jan 13, 2026

Description

Aave wants to track specific orders in bulk, knowing their ids.

Changes

Adds POST handler for v1/orders/lookup endpoint that requires a list of order uids and responds with a vector of their data. Has a hardcoded limit of 128 orders per request (to fit the MAX_JSON_BODY_PAYLOAD size).

How to test

Test on staging, query multiple orders using this API.

@m-sz m-sz marked this pull request as ready for review January 23, 2026 14:10
@m-sz m-sz requested a review from a team as a code owner January 23, 2026 14:10
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new endpoint v1/get_orders to retrieve orders in batches using a POST request with a limit of 5000 orders per request. The code adds a new module get_orders_by_uid.rs, modifies api.rs to include the new route, and updates database/orders.rs and orderbook.rs to support fetching multiple orders. I found a missing test case for the MAX_ORDERS_LIMIT validation.

@m-sz
Copy link
Contributor Author

m-sz commented Jan 23, 2026

Tested on sepolia staging:

curl -d "[\"0x4f30bff027049ef2d2f8c8ba17be5e2e452c8fc9ad48273ef44d48c0e210bbc16406ce57254d45d503c7cb3e729aab9ddbbb3ec269738a51\", \"0x4f30bff027049ef2d2f8c8ba17be5e2e452c8fc9ad48273ef44d48c0e210bbc16406ce57254d45d503c7cb3e729aab9ddbbb3ec269738a51\"]" "https://barn.api.cow.fi/sepolia/api/v1/orders/lookup" -H "Content-Type: application/json"

(queries twice for the same order)

response

[
    {
        "creationDate": "2026-01-23T14:18:53.912159Z",
        "owner": "0x6406ce57254d45d503c7cb3e729aab9ddbbb3ec2",
        "uid": "0x4f30bff027049ef2d2f8c8ba17be5e2e452c8fc9ad48273ef44d48c0e210bbc16406ce57254d45d503c7cb3e729aab9ddbbb3ec269738a51",
        "availableBalance": null,
        "executedBuyAmount": "3931710370705679403",
        "executedSellAmount": "4000000000000000000",
        "executedSellAmountBeforeFees": "4000000000000000000",
        "executedFeeAmount": "0",
        "executedFee": "68289629294320597",
        "executedFeeToken": "0xbe72e441bf55620febc26715db68d3494213d8cb",
        "invalidated": false,
        "status": "fulfilled",
        "class": "limit",
        "settlementContract": "0x9008d19f58aabd9ed0d60971565aa8510560ab41",
        "isLiquidityOrder": false,
        "fullAppData": "{\"appCode\":\"CoW Swap\",\"environment\":\"barn\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"quote\":{\"slippageBips\":101,\"smartSlippage\":true}},\"version\":\"1.12.0\"}",
        "quote": {
            "gasAmount": "98318.00000000000",
            "gasPrice": "3575917434.000000",
            "sellTokenPrice": "0.008587696541803472",
            "sellAmount": "4000000000000000000",
            "buyAmount": "4000000000000000000",
            "feeAmount": "40939622000450720",
            "solver": "0x99b4136666ca1d13020830350ca8d01a0e5e466b",
            "verified": true,
            "metadata": {
                "interactions": [],
                "jitOrders": [],
                "preInteractions": [],
                "version": "1.0"
            }
        },
        "sellToken": "0xbe72e441bf55620febc26715db68d3494213d8cb",
        "buyToken": "0xbe72e441bf55620febc26715db68d3494213d8cb",
        "receiver": "0x6406ce57254d45d503c7cb3e729aab9ddbbb3ec2",
        "sellAmount": "4000000000000000000",
        "buyAmount": "3919073868181753833",
        "validTo": 1769179729,
        "appData": "0x8a8e8bdd353e10a8fb1aabb9594537fba2f1cce8fc655fdeaaa568286b3b53af",
        "feeAmount": "0",
        "kind": "sell",
        "partiallyFillable": false,
        "sellTokenBalance": "erc20",
        "buyTokenBalance": "erc20",
        "signingScheme": "eip712",
        "signature": "0x6cc9e9e2e8826cffb092a8531b103bbc3e70472e6455185af20245d1be14748e13a440f8e19e88db1a47320cff5d7b4a5ac32f07c76f708f94531616f597e8411b",
        "interactions": {
            "pre": [],
            "post": []
        }
    },
    {
        "creationDate": "2026-01-23T14:18:53.912159Z",
        "owner": "0x6406ce57254d45d503c7cb3e729aab9ddbbb3ec2",
        "uid": "0x4f30bff027049ef2d2f8c8ba17be5e2e452c8fc9ad48273ef44d48c0e210bbc16406ce57254d45d503c7cb3e729aab9ddbbb3ec269738a51",
        "availableBalance": null,
        "executedBuyAmount": "3931710370705679403",
        "executedSellAmount": "4000000000000000000",
        "executedSellAmountBeforeFees": "4000000000000000000",
        "executedFeeAmount": "0",
        "executedFee": "68289629294320597",
        "executedFeeToken": "0xbe72e441bf55620febc26715db68d3494213d8cb",
        "invalidated": false,
        "status": "fulfilled",
        "class": "limit",
        "settlementContract": "0x9008d19f58aabd9ed0d60971565aa8510560ab41",
        "isLiquidityOrder": false,
        "fullAppData": "{\"appCode\":\"CoW Swap\",\"environment\":\"barn\",\"metadata\":{\"orderClass\":{\"orderClass\":\"market\"},\"quote\":{\"slippageBips\":101,\"smartSlippage\":true}},\"version\":\"1.12.0\"}",
        "quote": {
            "gasAmount": "98318.00000000000",
            "gasPrice": "3575917434.000000",
            "sellTokenPrice": "0.008587696541803472",
            "sellAmount": "4000000000000000000",
            "buyAmount": "4000000000000000000",
            "feeAmount": "40939622000450720",
            "solver": "0x99b4136666ca1d13020830350ca8d01a0e5e466b",
            "verified": true,
            "metadata": {
                "interactions": [],
                "jitOrders": [],
                "preInteractions": [],
                "version": "1.0"
            }
        },
        "sellToken": "0xbe72e441bf55620febc26715db68d3494213d8cb",
        "buyToken": "0xbe72e441bf55620febc26715db68d3494213d8cb",
        "receiver": "0x6406ce57254d45d503c7cb3e729aab9ddbbb3ec2",
        "sellAmount": "4000000000000000000",
        "buyAmount": "3919073868181753833",
        "validTo": 1769179729,
        "appData": "0x8a8e8bdd353e10a8fb1aabb9594537fba2f1cce8fc655fdeaaa568286b3b53af",
        "feeAmount": "0",
        "kind": "sell",
        "partiallyFillable": false,
        "sellTokenBalance": "erc20",
        "buyTokenBalance": "erc20",
        "signingScheme": "eip712",
        "signature": "0x6cc9e9e2e8826cffb092a8531b103bbc3e70472e6455185af20245d1be14748e13a440f8e19e88db1a47320cff5d7b4a5ac32f07c76f708f94531616f597e8411b",
        "interactions": {
            "pre": [],
            "post": []
        }
    }
]

@m-sz
Copy link
Contributor Author

m-sz commented Jan 23, 2026

By the way, we can have a discussion on what the endpoint itself should be.
This has to be a POST request since we need to provide JSON array of order ids and /v1/orders obviously creates an order so there has to be a different name

I got a couple suggestions from Claude

  1. Nested under a "batch" or "bulk" path
    POST /v1/batch/orders
    POST /v1/bulk/orders
    Groups batch operations separately from the main resource routes.
  2. Different verb-like suffix
    POST /v1/orders/fetch
    POST /v1/orders/get
    POST /v1/orders/list
    Less common but unambiguous.
  3. Underscore-prefixed action
    POST /v1/orders/_search
    POST /v1/orders/_mget
    Elasticsearch uses this convention. The underscore signals "this is a special action, not a resource."
  4. Hyphenated compound resource
    POST /v1/order-lookups
    POST /v1/order-queries
    Treats the bulk lookup as its own resource type.
  5. RPC-style separate namespace
    POST /v1/rpc/get-orders
    POST /v1/actions/get-orders

based on the above suggestions I have opted for a custom not-order-id-like name /v1/orders/lookup - to lookup many orders, but I am open for discussion and don't have a strong opinion.

@m-sz m-sz marked this pull request as draft January 26, 2026 12:25
@m-sz m-sz marked this pull request as ready for review February 5, 2026 13:58
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

The implementation adds a new endpoint to fetch orders in batches. A critical issue was found in crates/orderbook/src/database/orders.rs where await is used on a stream, which will cause a compilation error. The implementation also inefficiently collects two full vectors into memory; a suggestion is provided to fix the bug and improve performance by chaining the streams.

@m-sz m-sz marked this pull request as draft February 5, 2026 14:05
Copy link
Contributor

@fafk fafk left a comment

Choose a reason for hiding this comment

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

LGTM. The endpoint should be documented in openapi.yml too I think.

Copy link
Contributor

@jmg-duarte jmg-duarte left a comment

Choose a reason for hiding this comment

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

I didn't notice these in the last review

- `{"error": {"orderUid": "<UID>", "description": "<message>"}}` for orders that failed conversion
Orders that do not exist in the database will be missing from the response.
content:
application/x-json:
Copy link
Contributor

Choose a reason for hiding this comment

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

x-json?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahhh, Claude's artifact. Good catch!

Copy link
Contributor

Choose a reason for hiding this comment

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

x-json is still there.

Copy link
Contributor

@MartinquaXD MartinquaXD left a comment

Choose a reason for hiding this comment

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

Looks alright overall. Just some small nits.

pub const ORDER_UID_LIMIT: usize = 1024;
/// OrderUid is 56 bytes. When hex encoded as 0x prefixes Json string it is 116.
/// Chosen to be under the MAX_JSON_BODY_PAYLOAD size of 1024 * 16
pub const ORDER_UID_LIMIT: usize = 128;
Copy link
Contributor

Choose a reason for hiding this comment

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

This constant is also used here:

if cancellations.data.order_uids.len() > ORDER_UID_LIMIT {

1024 -> 128 is a breaking change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is not truly a breaking change since the MAX_JSON_BODY_PAYLOAD was limiting the cancellation data order uids anyway to 139 (The amount of order uids that fit with the SignedOrderCancellations). I decided to reuse the same limiting constant for both requests and rounded it up to 128.

If the 1024 (original) limit needs to be effective, We will need to increase the MAX_JSON_BODY_PAYLOAD, or do away with any order count limiting and just use MAX_JSON_BODY_PAYLOAD.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure I am following. From the main branch:

if cancellations.data.order_uids.len() > ORDER_UID_LIMIT {

pub order_uids: Vec<OrderUid>,

It is literally the maximum number of UIDs in the requests. It was 1024, and now it is 128. Did I miss anything?

Copy link
Contributor Author

@m-sz m-sz Feb 25, 2026

Choose a reason for hiding this comment

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

Yeah, there is a default body limit at the api_router layer:

    api_router
        .layer(DefaultBodyLimit::max(MAX_JSON_BODY_PAYLOAD as usize))

which limits the request body to 16kb which equals to about 139 orders in the case of order cancellation payload. Which ultimately was the breaking change limiting the effective amount of order uids to 139.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Either we can also discuss removing the ORDER_UID_LIMIT altogether in both instances since we have the body limit in place.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, thanks! I missed that. Does it mean that with this PR it will now be limited to 128 instead of 1024?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this PR it will be limited to 128 instead of 139 effectively. The previous limit of 1024 was unrealistically high considering the size of hex encoded OrderUid and the body limit of 16kb.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By the way, You can try it out for yourself as is currently:

This will fail, the body is larger than 16kb (despite the 1024 max order limit, we are sending 133)

curl -v -X DELETE \
    -d "`jq -n '{"orderUids": [range(133) | "0xa8061d917531484d528329e6d6b99cc8bb7020d3436cdd8f4def4c6a8e78203604501b9b1d52e67f6862d157e00d13419d2d6e95ffffffff"], "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "signingScheme": "eip712" }'`" \
https://api.cow.fi/mainnet/api/v1/orders

132 orders will not fail the size check:

curl -v -X DELETE \
    -d "`jq -n '{"orderUids": [range(132) | "0xa8061d917531484d528329e6d6b99cc8bb7020d3436cdd8f4def4c6a8e78203604501b9b1d52e67f6862d157e00d13419d2d6e95ffffffff"], "signature": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "signingScheme": "eip712" }'`" \
https://api.cow.fi/mainnet/api/v1/orders

Copy link
Contributor

Choose a reason for hiding this comment

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

I mean, the code you share will work with 128 limit instead of 1024, no?

    api_router
        .layer(DefaultBodyLimit::max(MAX_JSON_BODY_PAYLOAD as usize))

Before this PR, the MAX_JSON_BODY_PAYLOAD was 1024, which means this layer allowed a body with a size up to 1024. And right now this is 128.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

MAX_JSON_BODY_PAYLOAD was 16kb, and ORDER_UID_LIMIT was 1024 those are distinct. MAX_JSON_BODY_PAYLOAD effectively limits every request to every endpoint to be under 16kB, while ORDER_UID_LIMIT is a custom constant used today in cancellations, and with this PR in bulk order querying to limit amount of orders that can come in a request. The MAX_JSON_BODY_PAYLOAD or ORDER_UID_LIMIT both have a chance to fire at a specific request. If the request containing a large amount of orders exceeds the body payload limit, it will not even go into the handler.
Then in the handler the parsed orders are checked if they fit under the ORDER_UID_LIMIT.

As discussed on Slack, let's keep both limits in place, since the ORDER_UID_LIMIT tells the user exactly what is wrong and what is the amount of orders that can be submitted at once.

m-sz and others added 7 commits February 25, 2026 13:30
Co-authored-by: ilya <ilya@cow.fi>
Moves domain level error handling to domain specific code.
API handler now returns Internal server error generic message.

Added de-duplication logic to requested orders.
Copy link
Contributor

@squadgazzz squadgazzz left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for addressing all the comments!

@m-sz m-sz added this pull request to the merge queue Feb 26, 2026
Merged via the queue into main with commit cb58b60 Feb 26, 2026
19 checks passed
@m-sz m-sz deleted the get-orders-by-uid branch February 26, 2026 17:20
@github-actions github-actions bot locked and limited conversation to collaborators Feb 26, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants