Skip to content

Implement POST /v1/swap/calldata with approval-aware response#46

Merged
findolor merged 4 commits intomainfrom
2026-02-20-implement-swap-calldata
Feb 20, 2026
Merged

Implement POST /v1/swap/calldata with approval-aware response#46
findolor merged 4 commits intomainfrom
2026-02-20-implement-swap-calldata

Conversation

@findolor
Copy link
Collaborator

@findolor findolor commented Feb 20, 2026

Chained PRs

Motivation

See issues:

This is a standalone reimplementation of PR #38, which was originally part of a chained PR sequence. This PR carries the same functionality but is rebased onto the swap quote branch so it can be reviewed and merged independently.

The swap calldata endpoint was stubbed with todo!(). Users need a way to get ABI-encoded takeOrders4 calldata for on-chain swap execution after obtaining a quote. The endpoint must handle two states: when the taker needs to approve an ERC20 token first, and when the swap is ready to execute.

Solution

Updated the rain.orderbook submodule to latest main to bring in TakeOrdersRequest, TakeOrdersCalldataResult, and approval_info()/take_orders_info() APIs. Extracted a shared CHAIN_ID constant for Base network (8453) used across the codebase.

Extended the existing SwapDataSource trait with get_calldata, backed by the library's get_take_orders_calldata. The handler builds a TakeOrdersRequest from the caller's parameters and returns either:

  • Approval needed: to = spender (orderbook), data = empty, with approval details in the approvals array
  • Ready to execute: to = orderbook, data = encoded takeOrders4 calldata, empty approvals

Error mapping from RaindexError variants:

  • NoLiquidity / InsufficientLiquidity → 404 Not Found
  • SameTokenPair / NonPositiveAmount / NegativePriceCap / FromHexError / Float → 400 Bad Request
  • Everything else → 500 Internal Error

7 unit tests covering ready, needs-approval, not-found, bad-request, internal-error, 401 auth, and 500 client init failure scenarios.

Checks

By submitting this for review, I'm confirming I've done the following:

  • made this PR as small as possible
  • unit-tested any new functionality
  • linked any relevant issues or PRs
  • included screenshots (if this involves a front-end change)

Summary by CodeRabbit

  • New Features

    • Added calldata generation for swap operations with approval workflow support.
    • Introduced comprehensive error handling for liquidity and parameter validation scenarios.
  • Improvements

    • Enhanced internal error handling and type flexibility for better system diagnostics.

Brings in TakeOrdersRequest, TakeOrdersCalldataResult, and
approval_info/take_orders_info APIs needed for the calldata endpoint.
Add pub(crate) CHAIN_ID constant in main.rs and use it in tokens.rs
instead of a hardcoded value.
Clone is needed for the mock data source to return cloned error results.
The taker field identifies who will execute the swap on-chain.
Add get_calldata to SwapDataSource trait backed by the library's
get_take_orders_calldata. The handler builds a TakeOrdersRequest from
the caller's parameters and returns either approval instructions or
ready-to-submit takeOrders4 calldata.

Includes 7 unit tests covering ready, needs-approval, not-found,
bad-request, internal-error, 401 auth, and 500 client init scenarios.
@findolor findolor self-assigned this Feb 20, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

This PR introduces calldata endpoint functionality for swap operations by integrating with TakeOrders, adding a configurable chain ID constant, and extending the API response types to support approval and calldata information.

Changes

Cohort / File(s) Summary
Core Calldata Implementation
src/routes/swap/calldata.rs, src/routes/swap/mod.rs
Implements new get_calldata trait method for SwapDataSource with RaindexSwapDataSource integration; adds process_swap_calldata async function to construct TakeOrdersRequest and handle calldata/approval responses; includes error mapping helper and comprehensive test coverage for various scenarios.
Type & Schema Updates
src/types/swap.rs
Added taker field of type Address to SwapCalldataRequest struct with schema annotation for OpenAPI documentation.
Error Handling
src/error.rs
Extended ApiError enum to derive Clone trait in addition to Debug and thiserror::Error, enabling clonable error variants.
Configuration & Utilities
src/main.rs, src/routes/tokens.rs
Introduced crate-private CHAIN_ID constant (value 8453) in crate root; updated TARGET_CHAIN_ID in tokens module to reference this configurable constant instead of hardcoded value.
Test & Submodule Updates
src/routes/swap/quote.rs, lib/rain.orderbook
Updated test fixtures to include calldata_result field in MockSwapDataSource; bumped lib/rain.orderbook submodule reference to latest commit.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as post_swap_calldata
    participant Logic as process_swap_calldata
    participant Trait as SwapDataSource
    participant Impl as RaindexSwapDataSource
    participant Raindex as Raindex Client

    Client->>API: POST /swap/calldata (SwapCalldataRequest)
    API->>Logic: process_swap_calldata(ds, request)
    Logic->>Logic: Create TakeOrdersRequest from request fields
    Logic->>Trait: get_calldata(take_req)
    Trait->>Impl: get_calldata(take_req)
    Impl->>Raindex: get_take_orders_calldata(request)
    Raindex-->>Impl: Result (approvals or take_orders info)
    Impl->>Impl: Map result and construct SwapCalldataResponse
    Impl-->>Trait: SwapCalldataResponse
    Trait-->>Logic: SwapCalldataResponse
    Logic-->>API: SwapCalldataResponse
    API-->>Client: 200 OK / 404 No liquidity / Error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #33: Directly uses the SwapDataSource.get_calldata implementation and map_raindex_error helper introduced here to integrate with Raindex client/result types.
  • PR #2: Modifies the same API scaffolding (ApiError, swap/tokens/types modules) with overlapping changes to SwapDataSource trait and swap types.

Suggested reviewers

  • hardyjosh
  • 0xgleb

🐰 Calldata flows through the tubes so clean,
TakeOrders integration, the best we've seen,
Chain ID configurable, errors now clone,
The swap API grows, in data we've sown!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement POST /v1/swap/calldata with approval-aware response' directly and accurately summarizes the main change: implementing a swap calldata endpoint that returns approval-aware responses.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 2026-02-20-implement-swap-calldata

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

❤️ Share

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

@findolor findolor requested a review from hardyjosh February 20, 2026 09:02
@findolor
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/routes/swap/mod.rs (1)

82-124: Approval response has empty symbol field, preventing clients from displaying token names.

Line 103 sets symbol: String::new() because the underlying library's approval_info object doesn't provide symbol information. The Approval struct's OpenAPI schema shows example = "USDC", but clients receive an empty string. Either populate symbol by looking up the token address in a registry, document that clients must resolve symbols client-side, or update the schema example to reflect that approval symbols are empty.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/routes/swap/mod.rs` around lines 82 - 124, The approval response
currently sets Approval.symbol to an empty string in get_calldata when using
approval_info(), which prevents clients from showing token names; fix by
resolving the token symbol from a token registry/metadata source before
constructing the Approval (e.g., call your token lookup function or
TokenRegistry::get_symbol(approval_info.token()) and set symbol to the returned
value, falling back to an empty string or None if not found), update the code
that builds the Approval in get_calldata to use that resolved symbol, and if you
prefer not to perform server-side lookups instead document this behavior or
change the OpenAPI example for Approval to reflect that symbol may be empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/rain.orderbook`:
- Line 1: The PR references commit d9fff371 which is an untagged floating HEAD
and does not export the expected symbols (TakeOrdersRequest,
TakeOrdersCalldataResult, approval_info, take_orders_info); instead the module
exports BuyRequest, SellRequest, TakeOrdersApiResponse, ApprovalApiResponse, and
TakeOrdersReadyResponse. Verify and pin the correct revision: either switch the
dependency to the intended tagged commit or update the code to use the actual
exported symbols. Update any import/exports to match the real API (replace
references to
TakeOrdersRequest/TakeOrdersCalldataResult/approval_info/take_orders_info with
the corresponding real types or add adapter types/mappings), and ensure the
dependency reference is a stable tag or SHA to fix reproducibility.

---

Nitpick comments:
In `@src/routes/swap/mod.rs`:
- Around line 82-124: The approval response currently sets Approval.symbol to an
empty string in get_calldata when using approval_info(), which prevents clients
from showing token names; fix by resolving the token symbol from a token
registry/metadata source before constructing the Approval (e.g., call your token
lookup function or TokenRegistry::get_symbol(approval_info.token()) and set
symbol to the returned value, falling back to an empty string or None if not
found), update the code that builds the Approval in get_calldata to use that
resolved symbol, and if you prefer not to perform server-side lookups instead
document this behavior or change the OpenAPI example for Approval to reflect
that symbol may be empty.

@@ -1 +1 @@
Subproject commit 420bab786c49c3f2fb94e19d15826b0f79608525
Subproject commit d9fff371d75d9d3d25087d20c77209b430a5c248
Copy link

@coderabbitai coderabbitai bot Feb 20, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the submodule commit and expected API surface.

# 1. Show what the submodule currently points to (commit + any associated tag)
git submodule status lib/rain.orderbook

# 2. Check whether d9fff371 is reachable from a named tag in the submodule
cd lib/rain.orderbook && git describe --tags d9fff371d75d9d3d25087d20c77209b430a5c248 2>/dev/null || echo "No tag found for this commit"

# 3. Confirm the four required symbols are publicly exported
rg -n "pub.*TakeOrdersRequest\|pub.*TakeOrdersCalldataResult\|pub fn approval_info\|pub fn take_orders_info" lib/rain.orderbook

Repository: ST0x-Technology/st0x.rest.api

Length of output: 238


🏁 Script executed:

#!/bin/bash
# Check submodule configuration and verify commit in remote

# 1. Read .gitmodules to find the remote URL
echo "=== Submodule configuration ==="
cat .gitmodules | grep -A 3 "rain.orderbook"

# 2. Extract the submodule URL
SUBMODULE_URL=$(git config --file .gitmodules --get submodule.lib/rain.orderbook.url)
echo "Submodule URL: $SUBMODULE_URL"

# 3. Try to verify the commit exists in the remote using git ls-remote
if [ ! -z "$SUBMODULE_URL" ]; then
    echo ""
    echo "=== Verifying commit d9fff371 exists in remote ==="
    git ls-remote "$SUBMODULE_URL" d9fff371d75d9d3d25087d20c77209b430a5c248
fi

# 4. Check if it's a GitHub URL and try to access the API
if [[ "$SUBMODULE_URL" == *"github.com"* ]]; then
    echo ""
    echo "=== Attempting to verify via GitHub API ==="
    # Extract owner/repo from URL
    REPO_PATH=$(echo "$SUBMODULE_URL" | sed 's|.*github.com[:/]\(.*\)\.git$|\1|')
    echo "Repo path: $REPO_PATH"
    
    # Try to fetch commit info from GitHub
    curl -s "https://api.github.com/repos/$REPO_PATH/commits/d9fff371d75d9d3d25087d20c77209b430a5c248" | head -30
fi

Repository: ST0x-Technology/st0x.rest.api

Length of output: 2817


🏁 Script executed:

#!/bin/bash
# Verify commit content and search for tags

# 1. Check if there are any tags pointing to or near this commit
echo "=== Checking for tags in rain.orderbook repo ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/git/refs/tags" | \
  jq -r '.[] | select(.object.sha == "d9fff371d75d9d3d25087d20c77209b430a5c248") | .ref' || echo "No tags found for this commit"

# 2. List recent tags to see version scheme
echo ""
echo "=== Recent tags in rain.orderbook ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/git/refs/tags?per_page=10" | jq -r '.[].ref' | head -20

# 3. Search for the four expected symbols in the repository using raw GitHub content API
echo ""
echo "=== Searching for expected API symbols ==="

# Create a temp file for results
RESULTS_FILE="/tmp/api_search.txt"
> "$RESULTS_FILE"

# Search for TakeOrdersRequest
echo "Searching for TakeOrdersRequest..." >> "$RESULTS_FILE"
curl -s "https://api.github.com/search/code?q=repo:rainlanguage/rain.orderbook+TakeOrdersRequest+language:rust" | \
  jq -r '.items[0:3] | .[] | "\(.path): \(.name)"' >> "$RESULTS_FILE" 2>&1

echo "" >> "$RESULTS_FILE"
echo "Searching for TakeOrdersCalldataResult..." >> "$RESULTS_FILE"
curl -s "https://api.github.com/search/code?q=repo:rainlanguage/rain.orderbook+TakeOrdersCalldataResult+language:rust" | \
  jq -r '.items[0:3] | .[] | "\(.path): \(.name)"' >> "$RESULTS_FILE" 2>&1

echo "" >> "$RESULTS_FILE"
echo "Searching for approval_info..." >> "$RESULTS_FILE"
curl -s "https://api.github.com/search/code?q=repo:rainlanguage/rain.orderbook+approval_info+language:rust" | \
  jq -r '.items[0:3] | .[] | "\(.path): \(.name)"' >> "$RESULTS_FILE" 2>&1

echo "" >> "$RESULTS_FILE"
echo "Searching for take_orders_info..." >> "$RESULTS_FILE"
curl -s "https://api.github.com/search/code?q=repo:rainlanguage/rain.orderbook+take_orders_info+language:rust" | \
  jq -r '.items[0:3] | .[] | "\(.path): \(.name)"' >> "$RESULTS_FILE" 2>&1

cat "$RESULTS_FILE"

Repository: ST0x-Technology/st0x.rest.api

Length of output: 1437


🏁 Script executed:

#!/bin/bash
# Direct inspection of commit tree and file content

# 1. Get the commit tree structure
echo "=== Commit d9fff371 tree structure ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/git/trees/1e4837b8f74fccb6c898c9142dec92104de809a0?recursive=1" | \
  jq -r '.tree[] | select(.type == "blob") | .path' | grep -E "\.(rs|sol)$" | head -20

# 2. Search within the repo using a simpler pattern - check if files exist with these names
echo ""
echo "=== Checking main source files ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/contents/" | jq -r '.[] | select(.type == "dir") | .name'

# 3. Try a direct search on the main branch for the symbols
echo ""
echo "=== Searching main branch for symbols (using raw search) ==="
for symbol in "TakeOrdersRequest" "TakeOrdersCalldataResult" "approval_info" "take_orders_info"; do
    echo "Searching for: $symbol"
    curl -s -H "Accept: application/vnd.github.v3.raw" \
      "https://api.github.com/search/code?q=$symbol+repo:rainlanguage/rain.orderbook&per_page=1" | \
      head -5
    echo ""
done

Repository: ST0x-Technology/st0x.rest.api

Length of output: 1951


🏁 Script executed:

#!/bin/bash
# Access raw file content directly without authentication

# 1. Get the main lib.rs to see what's exported
echo "=== Checking main crate lib.rs for API exports ==="
curl -s "https://raw.githubusercontent.com/rainlanguage/rain.orderbook/d9fff371d75d9d3d25087d20c77209b430a5c248/crates/bindings/src/lib.rs" | head -100

echo ""
echo "=== Searching for TakeOrdersRequest in the commit ==="
curl -s "https://raw.githubusercontent.com/rainlanguage/rain.orderbook/d9fff371d75d9d3d25087d20c77209b430a5c248/crates/bindings/src/lib.rs" | grep -i "takeorders"

# 2. Check if there's a root lib.rs or main module
echo ""
echo "=== Checking root src structure ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/contents/src?ref=d9fff371d75d9d3d25087d20c77209b430a5c248" | jq -r '.[] | .name' 2>/dev/null || echo "No src directory at root"

# 3. List crates to identify the main one
echo ""
echo "=== Listing crates directory ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/contents/crates?ref=d9fff371d75d9d3d25087d20c77209b430a5c248" | jq -r '.[] | .name'

# 4. Check the main orderbook crate if it exists
echo ""
echo "=== Checking for orderbook crate ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/contents/crates/orderbook/src/lib.rs?ref=d9fff371d75d9d3d25087d20c77209b430a5c248" | jq -r '.content' 2>/dev/null | base64 -d | grep -A 5 -B 5 -i "takeorders\|approval_info\|take_orders_info" | head -40

Repository: ST0x-Technology/st0x.rest.api

Length of output: 1981


🏁 Script executed:

#!/bin/bash
# Check rest_api crate for the expected APIs

echo "=== Checking rest_api crate lib.rs ==="
curl -s "https://raw.githubusercontent.com/rainlanguage/rain.orderbook/d9fff371d75d9d3d25087d20c77209b430a5c248/crates/rest_api/src/lib.rs" | head -150

echo ""
echo "=== Searching for the four API symbols in rest_api ==="
for symbol in "TakeOrdersRequest" "TakeOrdersCalldataResult" "approval_info" "take_orders_info"; do
    echo "--- Searching for: $symbol ---"
    curl -s "https://raw.githubusercontent.com/rainlanguage/rain.orderbook/d9fff371d75d9d3d25087d20c77209b430a5c248/crates/rest_api/src/lib.rs" | grep -i "$symbol" && echo "FOUND" || echo "NOT FOUND"
done

# Also check if these might be in separate files
echo ""
echo "=== Listing rest_api/src files ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/contents/crates/rest_api/src?ref=d9fff371d75d9d3d25087d20c77209b430a5c248" | jq -r '.[] | .name'

Repository: ST0x-Technology/st0x.rest.api

Length of output: 447


🏁 Script executed:

#!/bin/bash
# Check rest_api main.rs and routes for the APIs

echo "=== Checking rest_api main.rs ==="
curl -s "https://raw.githubusercontent.com/rainlanguage/rain.orderbook/d9fff371d75d9d3d25087d20c77209b430a5c248/crates/rest_api/src/main.rs" | head -100

echo ""
echo "=== Listing routes directory ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/contents/crates/rest_api/src/routes?ref=d9fff371d75d9d3d25087d20c77209b430a5c248" | jq -r '.[] | .name'

echo ""
echo "=== Searching all rest_api source files for target symbols ==="
# Try to get all rust files in rest_api and search them
for file in "crates/rest_api/src/main.rs" "crates/rest_api/src/error.rs"; do
    echo "--- Checking $file ---"
    curl -s "https://raw.githubusercontent.com/rainlanguage/rain.orderbook/d9fff371d75d9d3d25087d20c77209b430a5c248/$file" | grep -E "TakeOrdersRequest|TakeOrdersCalldataResult|approval_info|take_orders_info" && echo "FOUND IN $file" || echo "NOT IN $file"
done

# Check routes files
echo ""
echo "=== Checking routes files ==="
curl -s "https://api.github.com/repos/rainlanguage/rain.orderbook/git/trees/d9fff371d75d9d3d25087d20c77209b430a5c248?recursive=1" | \
  jq -r '.tree[] | select(.path | contains("rest_api/src/routes")) | .path' | head -20

Repository: ST0x-Technology/st0x.rest.api

Length of output: 3145


Commit d9fff371 does not contain the expected APIs and is untagged; verify this is the correct commit for the stated objectives.

The commit points to a registry URL update (not API additions) and does not export TakeOrdersRequest, TakeOrdersCalldataResult, approval_info, or take_orders_info. The actual exports are BuyRequest, SellRequest, TakeOrdersApiResponse, ApprovalApiResponse, and TakeOrdersReadyResponse. Additionally, the commit is untagged (floating HEAD), creating reproducibility concerns. Confirm whether this is the intended commit or if a different revision should be pinned.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/rain.orderbook` at line 1, The PR references commit d9fff371 which is an
untagged floating HEAD and does not export the expected symbols
(TakeOrdersRequest, TakeOrdersCalldataResult, approval_info, take_orders_info);
instead the module exports BuyRequest, SellRequest, TakeOrdersApiResponse,
ApprovalApiResponse, and TakeOrdersReadyResponse. Verify and pin the correct
revision: either switch the dependency to the intended tagged commit or update
the code to use the actual exported symbols. Update any import/exports to match
the real API (replace references to
TakeOrdersRequest/TakeOrdersCalldataResult/approval_info/take_orders_info with
the corresponding real types or add adapter types/mappings), and ensure the
dependency reference is a stable tag or SHA to fix reproducibility.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the main commit hash for rain.orderbook which has those exports.

Copy link

Choose a reason for hiding this comment

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

@findolor, thank you for the clarification—I apologize for the confusion. If the commit contains the expected exports and the code compiles correctly with TakeOrdersRequest, TakeOrdersCalldataResult, approval_info, and take_orders_info, then my analysis was mistaken. The submodule update is fine as-is.


🧠 Learnings used
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 38
File: src/routes/swap/mod.rs:115-129
Timestamp: 2026-02-17T09:36:00.616Z
Learning: In the swap calldata endpoint (Rust, `src/routes/swap/mod.rs`), when the response includes a non-empty `approvals` array, the two-phase flow is intentional: the client performs the approval transaction first, then calls the endpoint again to retrieve the actual swap calldata. In the approval response, `to` is set to `approval.spender()` to signal the eventual target, and `data` is `Bytes::new()` (empty) to indicate "no transaction yet." This design is preferred over using `Address::ZERO` for clarity.

Base automatically changed from 2026-02-20-implement-swap-quote to main February 20, 2026 10:32
@findolor findolor merged commit a8200d8 into main Feb 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants