Skip to content

feat(trading): add externally signed order submission - #925

Open
raq0x wants to merge 2 commits into
mainfrom
feat/signerless-order-submission
Open

feat(trading): add externally signed order submission#925
raq0x wants to merge 2 commits into
mainfrom
feat/signerless-order-submission

Conversation

@raq0x

@raq0x raq0x commented Jul 15, 2026

Copy link
Copy Markdown

Summary

Adds a first-class, documented signer-less path to the Trading SDK: complete a swap with only an owner address and an externally-produced EIP-712 signature - no signer or private key is ever passed to the SDK. This is for integrators using cold wallets (Tangem), MPC/custody services (Fireblocks), or smart accounts, who sign the payload in their own environment and hand the signature back.

The flow already worked via low-level calls but was undocumented and required hand-assembling the sendOrder body. This PR makes it a supported, documented API (the P0 milestone).

Related: https://linear.app/cowswap/project/sdk-external-signer-less-order-signing-b006c53af4e0

New API surface (@cowprotocol/sdk-trading):

  • getOrderToSubmit(quoteResults) - builds the sendOrder body from a quote, everything except the signature.
  • sdk.postSignedOrder(orderToSubmit, signature) - uploads app-data and submits the externally signed order (also available as the standalone postSignedOrder(orderBookApi, orderToSubmit, signature)).
const quoteResults = await sdk.getQuoteOnly({ owner, ...tradeParameters })
const orderToSubmit = getOrderToSubmit(quoteResults)

// sign quoteResults.orderTypedData in your own environment (eth_signTypedData_v4)
const signature = await signExternally(quoteResults.orderTypedData)

const { orderId } = await sdk.postSignedOrder(orderToSubmit, signature)

Testing

  • Unit tests, including a signer/signer-less sendOrder parity test
  • Runnable examples: examples/nodejs/{ethers6,viem}/src/signerless.ts

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added signer-less trading flow with externally generated EIP-712 signatures.
    • Exposed new order helper and submission APIs (getOrderToSubmit, postSignedOrder) and a TradingSdk.postSignedOrder method.
  • Documentation

    • Updated trading docs with an end-to-end signer-less workflow and signing scheme guidance.
    • Added “Signer-less example” sections and run commands for Node.js (ethers v6, viem).
  • Tests

    • Added Jest coverage for order preparation, signing scheme validation, and signed-order submission/app-data upload behavior.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: ca497c93-f5cd-43a3-b34b-a6c51632298a

📥 Commits

Reviewing files that changed from the base of the PR and between 65a23f1 and f0a3b03.

📒 Files selected for processing (5)
  • examples/nodejs/ethers6/src/signerless.ts
  • examples/nodejs/viem/src/signerless.ts
  • packages/trading/README.md
  • packages/trading/src/getOrderToSubmit.test.ts
  • packages/trading/src/getOrderToSubmit.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/trading/README.md
  • packages/trading/src/getOrderToSubmit.test.ts
  • examples/nodejs/viem/src/signerless.ts

📝 Walkthrough

Walkthrough

The trading package adds helpers for constructing and submitting externally signed orders. It also exports the new APIs, documents the signer-less workflow, adds tests, and provides ethers v6 and viem Node.js examples.

Changes

Signer-less trading flow

Layer / File(s) Summary
Build externally signable orders
packages/trading/src/getOrderToSubmit.ts, packages/trading/src/getOrderToSubmit.test.ts, packages/trading/src/index.ts
Adds validated order construction for externally signed quotes, including owner, app-data, quote ID, signing scheme, and EIP-712 fields.
Post externally signed orders
packages/trading/src/postSignedOrder.ts, packages/trading/src/tradingSdk.ts, packages/trading/src/postSignedOrder.test.ts, packages/trading/src/index.ts
Adds app-data upload handling, signed order submission, SDK integration, exports, and coverage for submission behavior and request parity.
Document and demonstrate signer-less flows
packages/trading/README.md, examples/nodejs/ethers6/*, examples/nodejs/viem/*
Documents the workflow and adds runnable ethers v6 and viem examples with external typed-data signing.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Suggested reviewers: alfetopito

Sequence Diagram(s)

sequenceDiagram
  participant TradingSdk
  participant getOrderToSubmit
  participant ExternalSigner
  participant postSignedOrder
  participant OrderBookApi
  TradingSdk->>getOrderToSubmit: build order body from quote
  ExternalSigner->>ExternalSigner: sign order typed data
  TradingSdk->>postSignedOrder: submit order body and signature
  postSignedOrder->>OrderBookApi: upload app data when required
  postSignedOrder->>OrderBookApi: send order with signature
  OrderBookApi-->>TradingSdk: return order id and signing result
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% 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 clearly matches the main change: adding externally signed order submission to the trading SDK.
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.
✨ 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/signerless-order-submission

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 install timed out. The project may have too many dependencies for the sandbox.


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.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@github-actions

github-actions Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

📦 GitHub Packages Published

Last updated: Jul 23, 2026, 01:58:25 PM UTC

The following packages have been published to GitHub Packages with pre-release version pr-925-f0a3b036:

  • @cowprotocol/cow-sdk@9.2.2-pr-925-f0a3b036.0
  • @cowprotocol/sdk-app-data@5.3.1-pr-925-f0a3b036.0
  • @cowprotocol/sdk-bridging@4.2.2-pr-925-f0a3b036.0
  • @cowprotocol/sdk-common@0.12.0-pr-925-f0a3b036.0
  • @cowprotocol/sdk-composable@1.1.2-pr-925-f0a3b036.0
  • @cowprotocol/sdk-config@2.3.1-pr-925-f0a3b036.0
  • @cowprotocol/sdk-contracts-ts@3.2.1-pr-925-f0a3b036.0
  • @cowprotocol/sdk-cow-shed@0.4.1-pr-925-f0a3b036.0
  • @cowprotocol/sdk-ethers-v5-adapter@0.4.10-pr-925-f0a3b036.0
  • @cowprotocol/sdk-ethers-v6-adapter@0.4.10-pr-925-f0a3b036.0
  • @cowprotocol/sdk-flash-loans@3.2.2-pr-925-f0a3b036.0
  • @cowprotocol/sdk-order-book@4.0.1-pr-925-f0a3b036.0
  • @cowprotocol/sdk-order-signing@1.1.2-pr-925-f0a3b036.0
  • @cowprotocol/sdk-subgraph@1.2.0-pr-925-f0a3b036.0
  • @cowprotocol/sdk-trading@2.2.2-pr-925-f0a3b036.0
  • @cowprotocol/sdk-viem-adapter@0.3.24-pr-925-f0a3b036.0
  • @cowprotocol/sdk-weiroll@0.2.0-pr-925-f0a3b036.0

Installation

These packages require authentication to install from GitHub Packages. First, create a .npmrc file:

# Create .npmrc file in your project root
echo "@cowprotocol:registry=https://npm.pkg.github.com" > .npmrc
echo "//npm.pkg.github.com/:_authToken=YOUR_GITHUB_TOKEN" >> .npmrc

To get your GitHub token:

  1. Go to https://github.com/settings/tokens
  2. Click "Generate new token (classic)"
  3. Check only the "read:packages" scope
  4. Copy the token and replace YOUR_GITHUB_TOKEN in the .npmrc file

Then install any of the packages above, either by exact version (i.e. @cowprotocol/cow-sdk@9.2.2-pr-925-f0a3b036.0) or more conveniently by using the tag (@cowprotocol/cow-sdk@pr-925):

# Yarn
yarn add npm:@cowprotocol/cow-sdk@pr-925

# pnpm
pnpm install npm:@cowprotocol/cow-sdk@pr-925

# NPM
npm install npm:@cowprotocol/cow-sdk@pr-925

Update to the latest version (only if you used the tag)

Every commit will publish a new package. To upgrade to the latest version, run:

# Yarn
yarn upgrade @cowprotocol/cow-sdk

# pnpm
pnpm update @cowprotocol/cow-sdk

# NPM
npm update @cowprotocol/cow-sdk

View Packages

You can view the published packages at: https://github.com/cowprotocol/cow-sdk/packages

@raq0x

raq0x commented Jul 15, 2026

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Jul 15, 2026
@raq0x
raq0x marked this pull request as ready for review July 15, 2026 12:01

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 (5)
examples/nodejs/ethers6/README.md (1)

39-41: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

Specifying a language for fenced code blocks improves syntax highlighting and satisfies markdown linters.

♻️ Proposed refactor
-```
+```sh
 pnpm --filter example-nodejs-ethers6 dev:signerless
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/nodejs/ethers6/README.md around lines 39 - 41, Update the fenced
code block containing the pnpm command in the README to specify the shell
language identifier, using the existing command unchanged.


</details>

<!-- cr-comment:v1:ab04d333676c6735d1f4441e -->

</blockquote></details>
<details>
<summary>examples/nodejs/viem/src/signerless.ts (2)</summary><blockquote>

`53-53`: _🎯 Functional Correctness_ | _🔵 Trivial_ | _💤 Low value_

**Use `parseUnits` for robust token amount calculation.**

Replace the floating-point calculation with `parseUnits` for accurate conversion.



<details>
<summary>♻️ Proposed refactor</summary>

```diff
-  const amount = BigInt(Math.floor(DEFAULT_SELL_AMOUNT * 10 ** WETH.decimals)).toString()
+  const amount = parseUnits(DEFAULT_SELL_AMOUNT, WETH.decimals).toString()
🤖 Prompt for 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.

In `@examples/nodejs/viem/src/signerless.ts` at line 53, Update the amount
calculation in signerless.ts to use viem’s parseUnits with the sell amount and
WETH.decimals, replacing the floating-point Math.floor and BigInt conversion
while preserving the resulting token-unit string.

29-30: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Use a string for the token amount and parseUnits to avoid precision loss.

Using a JavaScript number and floating-point arithmetic for token amounts can lead to precision loss. Consider defining DEFAULT_SELL_AMOUNT as a string and using viem's parseUnits helper.

♻️ Proposed refactor

First, add parseUnits to the viem import at the top of the file:

import { createPublicClient, http, parseUnits, type TypedDataDefinition } from 'viem'

Then update the amount constant:

-const DEFAULT_SELL_AMOUNT = 0.1 // WETH amount
+const DEFAULT_SELL_AMOUNT = '0.1' // WETH amount
 // ===============================================================
🤖 Prompt for 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.

In `@examples/nodejs/viem/src/signerless.ts` around lines 29 - 30, Update
DEFAULT_SELL_AMOUNT to a string value and use viem’s parseUnits when converting
it to the token amount, adding parseUnits to the existing viem imports. Ensure
the resulting amount uses the token’s appropriate decimals and avoid JavaScript
floating-point arithmetic throughout the sell flow.
examples/nodejs/viem/README.md (1)

40-42: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

Specifying a language for fenced code blocks improves syntax highlighting and satisfies markdown linters.

♻️ Proposed refactor
-```
+```sh
 pnpm --filter example-nodejs-viem dev:signerless
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @examples/nodejs/viem/README.md around lines 40 - 42, Update the fenced code
block containing the pnpm command to specify the shell language identifier,
preserving the command unchanged.


</details>

<!-- cr-comment:v1:25c51cfbb1c6761f54a4c130 -->

</blockquote></details>
<details>
<summary>examples/nodejs/ethers6/src/signerless.ts (1)</summary><blockquote>

`50-50`: _🎯 Functional Correctness_ | _🔵 Trivial_ | _💤 Low value_

**Use `parseUnits` to avoid floating-point precision loss.**

Using `Number` and `Math.round` for token amounts can lead to precision loss due to JavaScript's floating-point arithmetic. Consider using `ethers.parseUnits` to reliably handle token decimals.



<details>
<summary>♻️ Proposed refactor</summary>

First, add `parseUnits` to the `ethers` import at the top of the file:
```ts
import { JsonRpcProvider, Wallet, parseUnits } from 'ethers'

Then update the amount calculation:

-  const amount = Math.round(Number(DEFAULT_SELL_AMOUNT) * 10 ** WETH.decimals).toString()
+  const amount = parseUnits(DEFAULT_SELL_AMOUNT, WETH.decimals).toString()
🤖 Prompt for 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.

In `@examples/nodejs/ethers6/src/signerless.ts` at line 50, Update the amount
calculation in signerless.ts to use ethers’ parseUnits instead of Number and
Math.round, preserving DEFAULT_SELL_AMOUNT and WETH.decimals as the value and
unit inputs so token amounts are represented without floating-point precision
loss. Add parseUnits to the existing ethers import.
🤖 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 `@packages/trading/src/getOrderToSubmit.ts`:
- Around line 56-60: Update the native-token guard in getOrderToSubmit to
inspect the authoritative orderToSign.sellToken rather than
getIsEthFlowOrder(tradeParameters) or other quote metadata. Ensure only orders
whose signed sell token is native are rejected, while valid ERC-20 orders
proceed.

---

Nitpick comments:
In `@examples/nodejs/ethers6/README.md`:
- Around line 39-41: Update the fenced code block containing the pnpm command in
the README to specify the shell language identifier, using the existing command
unchanged.

In `@examples/nodejs/ethers6/src/signerless.ts`:
- Line 50: Update the amount calculation in signerless.ts to use ethers’
parseUnits instead of Number and Math.round, preserving DEFAULT_SELL_AMOUNT and
WETH.decimals as the value and unit inputs so token amounts are represented
without floating-point precision loss. Add parseUnits to the existing ethers
import.

In `@examples/nodejs/viem/README.md`:
- Around line 40-42: Update the fenced code block containing the pnpm command to
specify the shell language identifier, preserving the command unchanged.

In `@examples/nodejs/viem/src/signerless.ts`:
- Line 53: Update the amount calculation in signerless.ts to use viem’s
parseUnits with the sell amount and WETH.decimals, replacing the floating-point
Math.floor and BigInt conversion while preserving the resulting token-unit
string.
- Around line 29-30: Update DEFAULT_SELL_AMOUNT to a string value and use viem’s
parseUnits when converting it to the token amount, adding parseUnits to the
existing viem imports. Ensure the resulting amount uses the token’s appropriate
decimals and avoid JavaScript floating-point arithmetic throughout the sell
flow.
🪄 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

Run ID: ba9c542e-febe-4e5a-8fce-788b13f5e751

📥 Commits

Reviewing files that changed from the base of the PR and between fa73993 and 65a23f1.

📒 Files selected for processing (13)
  • examples/nodejs/ethers6/README.md
  • examples/nodejs/ethers6/package.json
  • examples/nodejs/ethers6/src/signerless.ts
  • examples/nodejs/viem/README.md
  • examples/nodejs/viem/package.json
  • examples/nodejs/viem/src/signerless.ts
  • packages/trading/README.md
  • packages/trading/src/getOrderToSubmit.test.ts
  • packages/trading/src/getOrderToSubmit.ts
  • packages/trading/src/index.ts
  • packages/trading/src/postSignedOrder.test.ts
  • packages/trading/src/postSignedOrder.ts
  • packages/trading/src/tradingSdk.ts

Comment on lines +56 to +60
if (getIsEthFlowOrder(tradeParameters)) {
throw new Error(
'Orders selling the native token cannot be submitted to the order book. Use getEthFlowTransaction instead.',
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check the signed order’s sell token instead of quote metadata.

Line 56 can disagree with the payload being submitted when deserialized inputs contain stale tradeParameters. Use the authoritative orderToSign.sellToken; otherwise a native-token order may pass this guard, or a valid ERC-20 order may be rejected.

Proposed fix
-  if (getIsEthFlowOrder(tradeParameters)) {
+  if (getIsEthFlowOrder(orderToSign)) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (getIsEthFlowOrder(tradeParameters)) {
throw new Error(
'Orders selling the native token cannot be submitted to the order book. Use getEthFlowTransaction instead.',
)
}
if (getIsEthFlowOrder(orderToSign)) {
throw new Error(
'Orders selling the native token cannot be submitted to the order book. Use getEthFlowTransaction instead.',
)
}
🤖 Prompt for 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.

In `@packages/trading/src/getOrderToSubmit.ts` around lines 56 - 60, Update the
native-token guard in getOrderToSubmit to inspect the authoritative
orderToSign.sellToken rather than getIsEthFlowOrder(tradeParameters) or other
quote metadata. Ensure only orders whose signed sell token is native are
rejected, while valid ERC-20 orders proceed.

@raq0x
raq0x requested review from a team, alfetopito and shoom3301 July 15, 2026 13:28
)
}

if (getIsEthFlowOrder(tradeParameters)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In fact, we use only two fields from tradeParameters: owner and sellToken.
sellToken is already present in orderToSign.
My concern is that we need to minimize the parameters of getOrderToSubmit function as much as possible.
It looks like we can get rid of tradeParameters by replacing it with just owner.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Same thing with quoteResponse, we only use id from it here

@raq0x raq0x Jul 20, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point, thanks, you're right.
One thing I want to ask before doing it, the ticket (https://linear.app/cowswap/project/sdk-external-signer-less-order-signing-b006c53af4e0/overview#scope-proposed-work-3d2fdecd) proposes the happy path as sdk.getOrderToSubmit(quoteResults) - i.e. hand it the whole quote result you already got from getQuoteOnly. Dropping to { orderToSign, appDataInfo, owner, quoteId } requires less arguments, but pushes destructuring onto every caller, which moves away from that recipe.

Do you think we should do it?

return {
...orderToSign,
from: owner,
signingScheme: SigningScheme.EIP712,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I believe it can be any of SigningScheme

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point, I should just have it as optional argument (default to EIP712). P3 will cover EIP1271 in the future

@raq0x
raq0x requested a review from azebuado July 24, 2026 07:31
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