Skip to content

feat: Layer 2 agent swap facade (swap_quote/status/execute/cancel)#7

Merged
BarisSozen merged 12 commits into
mainfrom
feat/agent-friendly-layer2
May 19, 2026
Merged

feat: Layer 2 agent swap facade (swap_quote/status/execute/cancel)#7
BarisSozen merged 12 commits into
mainfrom
feat/agent-friendly-layer2

Conversation

@BarisSozen
Copy link
Copy Markdown
Member

@BarisSozen BarisSozen commented May 19, 2026

Summary

Layer 2 of the agent-friendly MCP work: a single-call swap facade over the existing sealed-bid RFQ primitives, so an AI agent can run a full OTC swap end-to-end via MCP (previously impossible — there was no accept_quote/list_quotes tool).

  • 4 new tools: swap_quote (open sealed-bid RFQ + brief bounded wait for first bids → swap_handle + best bid), swap_status (stateless re-poll / context recovery), swap_execute (directional-limit-protected accept → trade), swap_cancel (clean abort).
  • Async-coherent, not a sync price. There is deliberately no public synchronous quote — that would reconstruct the exact price oracle the sealed-bid Ghost Auction exists to destroy. The facade packages the existing owner-only auction into one-call ergonomics with zero new GraphQL/maker-visible surface → sealed-bid privacy property structurally unchanged.
  • Sealed reservation: limit_price (SELL=floor, BUY=ceiling) is never sent to makers and never persisted (correct commit-reveal); re-supplied at execute.
  • private (Ghost Auction) defaults ON for the automated channel; overridable.
  • No backend change, no SDK bump (@hashlock-tech/sdk stays pinned ^0.1.4). create_rfq is byte-untouched (its intent-compiler + pin tests are load-bearing).
  • All orchestration in one pure, dependency-injected src/lib/swap.ts (decimal compare, directional limit gate, fail-closed price validation, best-bid selection, bounded poll, the 4 run* flows); index.ts adds thin wrapTool+idempotency-scoped registrations.

Security hardening (from adversarial review)

  • Uniform not-found contract: a forbidden/non-participant RFQ collapses to the same SWAP_NOT_FOUND as an unknown handle across swap_execute/swap_status/swap_cancel (closes an RFQ-existence/participant oracle). swap_quote opens a new RFQ so has no such surface.
  • Fail-closed price math: isPositiveDecimal gates maker price/amount and the agent limit_price, so price safety does not silently depend on backend input sanitization.
  • accepted_price/accepted_amount taken from the selected quote (the SDK acceptQuote return carries no price). Idempotency-wrapped writes. Agent-facing warning that accepted_amount may exceed the requested amount under full-fill v1.

Test Plan

  • pnpm install --frozen-lockfile --ignore-workspace (nested-clone standalone parity)
  • pnpm test119/119 pass, 6 files (TDD per task; money path adversarially tested both BUY/SELL × both bound directions, all outcome branches, forbidden-collapse parity, fail-closed price)
  • pnpm lint (tsc --noEmit) clean
  • pnpm build (tsup) clean — dist/index.js ~40 KB
  • All pre-existing Layer-1 tests green incl. every create_rfq intent-compiler pin (byte-stability guard)
  • CodeRabbit review (this PR)
  • Operator: npm publish 0.4.0 + MCP-registry publish (server.json manifest-rev 1.4.0 > live 1.3.0; npm 0.4.0 in packages[].version)

Notes

  • Design spec + implementation plan live in the hashlock-markets monorepo (docs/superpowers/), shipped separately; web/public/llms*.txt updated there too.
  • Two INFO-level items from final review (intentional): some swap.ts types have only internal consumers (library API surface); getQuotes runs before the limit_price grammar check on the malformed path (no money moves — test-proven).
  • version bumped to 0.4.0 (npm) — 0.3.0 is already published, so the fix must ship as 0.4.0.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added four swap tools to create RFQs, poll for quotes (bounded wait), execute with limit-price/confirmation semantics and idempotency, query status, and cancel swaps. Private/ghost-auction default enabled; execution validates limits and normalizes common error outcomes.
  • Chores

    • Bumped server/package version to 0.4.0.
  • Tests

    • Added comprehensive tests covering quoting, selection, polling, execute/status/cancel flows, input validation, and tool descriptions.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e0dc42bc-7550-4ef9-baa0-58986d4ae479

📥 Commits

Reviewing files that changed from the base of the PR and between fd0837d and 14b1d5f.

📒 Files selected for processing (2)
  • src/__tests__/swap.test.ts
  • src/lib/swap.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/swap.test.ts
  • src/lib/swap.ts

📝 Walkthrough

Walkthrough

This PR adds a complete swap trading facade to the HashLock MCP server, introducing four new MCP tools—swap_quote, swap_status, swap_execute, and swap_cancel—built on string-safe decimal arithmetic, deterministic quote selection, and fail-closed error handling that unifies forbidden/unauthorized errors across the abstracted SwapClient.

Changes

Swap Quoting and Execution Facade

Layer / File(s) Summary
Swap domain model and data contracts
src/lib/swap.ts
SwapRfq, SwapQuote, and SwapClient interfaces define the abstract trading domain; Side type, Remember/Sleep utility types, and constants establish polling/selection behavior.
String-decimal arithmetic and quote selection
src/lib/swap.ts, src/__tests__/swap.test.ts
compareDecimal, limitSatisfied, isPositiveDecimal, and selectBestBid implement string-safe numeric operations without float parsing; tests verify ordering edge cases, directional acceptance logic (SELL floor, BUY ceiling), eligibility filters, and best-quote selection rules by side.
Quote polling and wait-window management
src/lib/swap.ts, src/__tests__/swap.test.ts
pollForQuotes repeatedly fetches quotes on a fixed interval and returns once an eligible quote appears or max wait window elapses; tests validate early termination, bounded-window stopping, and extreme input clamping.
Swap operation handlers
src/lib/swap.ts, src/__tests__/swap.test.ts
runSwapQuote, runSwapExecute, runSwapCancel, and runSwapStatus orchestrate RFQ lifecycle: quote creation and bid polling; execution with limit_price or quote_id selection and confirmation; cancellation with uniform not-found mapping; and status reconstruction with open-state polling. Tests cover confirmation/selection behavior, limit satisfaction for both sides, terminal/unknown-handle errors, malformed price inputs, and forbidden-to-SWAP_NOT_FOUND parity.
MCP tool registration and integration
src/index.ts, src/__tests__/tools.test.ts
Casts HashLock instance to SwapClient, registers four MCP tools with Zod input schemas, routes each through corresponding handlers with idempotency keying by operation and parameters, and adds realSleep helper for deterministic polling. Tool description strings are pinned and verified to contain routing markers, limit_price semantics, and private/Ghost Auction defaults.
Version metadata updates
package.json, server.json
package.json version bumped from 0.3.0 to 0.4.0; server.json MCP version bumped from 1.3.0 to 1.4.0 and @hashlock-tech/mcp package version bumped from 0.3.0 to 0.4.0.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MCP_Tool as MCP Tool (swap_*)
  participant SwapHandler as runSwap* handler
  participant SwapClient as SwapClient (createRFQ/getQuotes/acceptQuote/cancelRFQ/getRFQ)

  Client->>MCP_Tool: submit swap_quote / swap_execute / swap_status / swap_cancel
  MCP_Tool->>SwapHandler: invoke runSwapQuote|runSwapExecute|runSwapStatus|runSwapCancel
  SwapHandler->>SwapClient: createRFQ (for quote) / getRFQ (status/execute) / getQuotes (poll) / cancelRFQ (cancel)
  SwapClient-->>SwapHandler: RFQ / quotes / accept result / cancel result
  SwapHandler-->>MCP_Tool: ToolContent result (best_bid / acceptance / status / cancel)
  MCP_Tool-->>Client: response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Hashlock-Tech/hashlock-mcp#4: The idempotency and tool-registration infrastructure (idempotency.remember, MCP tool wiring) introduced there is reused and extended by this swap facade.

Poem

🐰 Soft paws tap keys for RFQs bright,

Strings, not floats, keep prices tight,
Poll and pick the best small bid,
Four new tools run, tidy and swift,
Hops of joy when swaps are lit.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Layer 2 agent swap facade (swap_quote/status/execute/cancel)' directly and specifically summarizes the main change: adding a new Layer 2 agent-facing swap facade with four new tools. The title is clear, concise, and accurately reflects the primary contribution of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent-friendly-layer2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

Copy link
Copy Markdown

@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: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/swap.ts`:
- Around line 175-177: Reject calls to swap_execute when both args.quote_id and
args.limit_price are supplied: detect the ambiguous case (args.quote_id &&
args.limit_price !== undefined) and return an explicit error (rather than
silently preferring quote_id) explaining the conflict and instructing the caller
to supply only one confirmation method; apply the same check in the other
similar branch that currently handles real-funds confirmation (the branch that
returns CONFIRMATION_REQUIRED/okContent) so both places consistently reject
ambiguous inputs.
- Line 138: The code computes still_open using the original rfq (created by
createRFQ) which is stale; change the computation to use the post-poll RFQ state
(the RFQ returned by the polling function, e.g., the variable assigned from
pollRFQ or rfqAfterPoll) instead of the initial rfq so RFQ_OPEN_STATES.has(...)
checks the latest status; update the object that sets still_open to reference
that polled RFQ's .status.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 03047406-06b5-4084-9eae-108fc9dad08b

📥 Commits

Reviewing files that changed from the base of the PR and between 35ddecb and fd0837d.

📒 Files selected for processing (6)
  • package.json
  • server.json
  • src/__tests__/swap.test.ts
  • src/__tests__/tools.test.ts
  • src/index.ts
  • src/lib/swap.ts

Comment thread src/lib/swap.ts Outdated
Comment thread src/lib/swap.ts
…rate still_open post-poll (CodeRabbit PR#7)
@BarisSozen BarisSozen merged commit 1ca37bf into main May 19, 2026
4 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.

1 participant